@steipete/oracle 0.8.6 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +130 -45
- package/dist/bin/oracle-cli.js +613 -379
- package/dist/bin/oracle-mcp.js +2 -2
- package/dist/bin/oracle.js +165 -279
- package/dist/scripts/agent-send.js +31 -31
- package/dist/scripts/check.js +6 -6
- package/dist/scripts/debug/extract-chatgpt-response.js +10 -10
- package/dist/scripts/docs-list.js +30 -30
- package/dist/scripts/git-policy.js +25 -23
- package/dist/scripts/run-cli.js +8 -8
- package/dist/scripts/runner.js +203 -195
- package/dist/scripts/test-browser.js +21 -18
- package/dist/scripts/test-remote-chrome.js +20 -20
- package/dist/src/bridge/connection.js +18 -18
- package/dist/src/bridge/userConfigFile.js +7 -7
- package/dist/src/browser/actions/assistantResponse.js +149 -101
- package/dist/src/browser/actions/attachmentDataTransfer.js +49 -47
- package/dist/src/browser/actions/attachments.js +246 -150
- package/dist/src/browser/actions/domEvents.js +2 -2
- package/dist/src/browser/actions/modelSelection.js +314 -104
- package/dist/src/browser/actions/navigation.js +161 -136
- package/dist/src/browser/actions/promptComposer.js +100 -64
- package/dist/src/browser/actions/remoteFileTransfer.js +10 -10
- package/dist/src/browser/actions/thinkingTime.js +207 -110
- package/dist/src/browser/chromeLifecycle.js +62 -60
- package/dist/src/browser/config.js +34 -15
- package/dist/src/browser/constants.js +17 -12
- package/dist/src/browser/cookies.js +19 -19
- package/dist/src/browser/detect.js +62 -62
- package/dist/src/browser/domDebug.js +1 -1
- package/dist/src/browser/index.js +452 -303
- package/dist/src/browser/modelStrategy.js +1 -1
- package/dist/src/browser/pageActions.js +5 -5
- package/dist/src/browser/policies.js +16 -13
- package/dist/src/browser/profileState.js +44 -39
- package/dist/src/browser/prompt.js +72 -42
- package/dist/src/browser/promptSummary.js +5 -5
- package/dist/src/browser/providerDomFlow.js +17 -0
- package/dist/src/browser/providers/chatgptDomProvider.js +49 -0
- package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +254 -0
- package/dist/src/browser/providers/index.js +2 -0
- package/dist/src/browser/reattach.js +67 -34
- package/dist/src/browser/reattachHelpers.js +31 -26
- package/dist/src/browser/sessionRunner.js +37 -25
- package/dist/src/browser/utils.js +9 -9
- package/dist/src/browserMode.js +1 -1
- package/dist/src/cli/bridge/claudeConfig.js +16 -16
- package/dist/src/cli/bridge/client.js +28 -20
- package/dist/src/cli/bridge/codexConfig.js +16 -16
- package/dist/src/cli/bridge/doctor.js +47 -39
- package/dist/src/cli/bridge/host.js +58 -56
- package/dist/src/cli/browserConfig.js +65 -45
- package/dist/src/cli/browserDefaults.js +27 -26
- package/dist/src/cli/bundleWarnings.js +1 -1
- package/dist/src/cli/clipboard.js +11 -2
- package/dist/src/cli/detach.js +7 -4
- package/dist/src/cli/dryRun.js +29 -25
- package/dist/src/cli/duplicatePromptGuard.js +3 -3
- package/dist/src/cli/engine.js +9 -9
- package/dist/src/cli/errorUtils.js +1 -1
- package/dist/src/cli/fileSize.js +11 -0
- package/dist/src/cli/format.js +2 -2
- package/dist/src/cli/help.js +28 -28
- package/dist/src/cli/hiddenAliases.js +3 -3
- package/dist/src/cli/markdownBundle.js +12 -8
- package/dist/src/cli/markdownRenderer.js +15 -15
- package/dist/src/cli/notifier.js +77 -67
- package/dist/src/cli/options.js +145 -87
- package/dist/src/cli/oscUtils.js +1 -1
- package/dist/src/cli/promptRequirement.js +2 -2
- package/dist/src/cli/renderOutput.js +1 -1
- package/dist/src/cli/rootAlias.js +1 -1
- package/dist/src/cli/runOptions.js +37 -25
- package/dist/src/cli/sessionCommand.js +31 -21
- package/dist/src/cli/sessionDisplay.js +182 -79
- package/dist/src/cli/sessionLineage.js +60 -0
- package/dist/src/cli/sessionRunner.js +118 -90
- package/dist/src/cli/sessionTable.js +28 -24
- package/dist/src/cli/stdin.js +22 -0
- package/dist/src/cli/tagline.js +121 -124
- package/dist/src/cli/tui/index.js +140 -127
- package/dist/src/cli/writeOutputPath.js +5 -5
- package/dist/src/config.js +7 -7
- package/dist/src/gemini-web/browserSessionManager.js +80 -0
- package/dist/src/gemini-web/client.js +81 -64
- package/dist/src/gemini-web/executionMode.js +16 -0
- package/dist/src/gemini-web/executor.js +327 -169
- package/dist/src/gemini-web/index.js +1 -1
- package/dist/src/mcp/server.js +16 -12
- package/dist/src/mcp/tools/consult.js +81 -64
- package/dist/src/mcp/tools/sessionResources.js +12 -12
- package/dist/src/mcp/tools/sessions.js +26 -17
- package/dist/src/mcp/types.js +5 -5
- package/dist/src/mcp/utils.js +15 -7
- package/dist/src/oracle/background.js +15 -15
- package/dist/src/oracle/claude.js +53 -25
- package/dist/src/oracle/client.js +84 -46
- package/dist/src/oracle/config.js +124 -58
- package/dist/src/oracle/errors.js +38 -38
- package/dist/src/oracle/files.js +69 -45
- package/dist/src/oracle/finishLine.js +10 -8
- package/dist/src/oracle/format.js +3 -3
- package/dist/src/oracle/gemini.js +37 -30
- package/dist/src/oracle/logging.js +7 -7
- package/dist/src/oracle/markdown.js +28 -28
- package/dist/src/oracle/modelResolver.js +16 -16
- package/dist/src/oracle/multiModelRunner.js +12 -12
- package/dist/src/oracle/oscProgress.js +8 -8
- package/dist/src/oracle/promptAssembly.js +6 -3
- package/dist/src/oracle/request.js +23 -15
- package/dist/src/oracle/run.js +172 -140
- package/dist/src/oracle/runUtils.js +8 -5
- package/dist/src/oracle/tokenEstimate.js +6 -6
- package/dist/src/oracle/tokenStats.js +5 -5
- package/dist/src/oracle/tokenStringifier.js +5 -5
- package/dist/src/oracle.js +12 -12
- package/dist/src/oracleHome.js +3 -3
- package/dist/src/remote/client.js +25 -25
- package/dist/src/remote/health.js +20 -20
- package/dist/src/remote/remoteServiceConfig.js +9 -9
- package/dist/src/remote/server.js +129 -118
- package/dist/src/sessionManager.js +81 -75
- package/dist/src/sessionStore.js +3 -3
- package/dist/src/version.js +10 -10
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/dist/vendor/oracle-notifier/README.md +2 -0
- package/package.json +69 -65
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/vendor/oracle-notifier/README.md +2 -0
- package/dist/markdansi/types/index.js +0 -4
- package/dist/oracle/bin/oracle-cli.js +0 -472
- package/dist/oracle/src/browser/actions/assistantResponse.js +0 -471
- package/dist/oracle/src/browser/actions/attachments.js +0 -82
- package/dist/oracle/src/browser/actions/modelSelection.js +0 -190
- package/dist/oracle/src/browser/actions/navigation.js +0 -75
- package/dist/oracle/src/browser/actions/promptComposer.js +0 -167
- package/dist/oracle/src/browser/chromeLifecycle.js +0 -104
- package/dist/oracle/src/browser/config.js +0 -33
- package/dist/oracle/src/browser/constants.js +0 -40
- package/dist/oracle/src/browser/cookies.js +0 -210
- package/dist/oracle/src/browser/domDebug.js +0 -36
- package/dist/oracle/src/browser/index.js +0 -331
- package/dist/oracle/src/browser/pageActions.js +0 -5
- package/dist/oracle/src/browser/prompt.js +0 -88
- package/dist/oracle/src/browser/promptSummary.js +0 -20
- package/dist/oracle/src/browser/sessionRunner.js +0 -80
- package/dist/oracle/src/browser/utils.js +0 -62
- package/dist/oracle/src/browserMode.js +0 -1
- package/dist/oracle/src/cli/browserConfig.js +0 -44
- package/dist/oracle/src/cli/dryRun.js +0 -59
- package/dist/oracle/src/cli/engine.js +0 -17
- package/dist/oracle/src/cli/errorUtils.js +0 -9
- package/dist/oracle/src/cli/help.js +0 -70
- package/dist/oracle/src/cli/markdownRenderer.js +0 -15
- package/dist/oracle/src/cli/options.js +0 -103
- package/dist/oracle/src/cli/promptRequirement.js +0 -14
- package/dist/oracle/src/cli/rootAlias.js +0 -30
- package/dist/oracle/src/cli/sessionCommand.js +0 -77
- package/dist/oracle/src/cli/sessionDisplay.js +0 -270
- package/dist/oracle/src/cli/sessionRunner.js +0 -94
- package/dist/oracle/src/heartbeat.js +0 -43
- package/dist/oracle/src/oracle/client.js +0 -48
- package/dist/oracle/src/oracle/config.js +0 -29
- package/dist/oracle/src/oracle/errors.js +0 -101
- package/dist/oracle/src/oracle/files.js +0 -220
- package/dist/oracle/src/oracle/format.js +0 -33
- package/dist/oracle/src/oracle/fsAdapter.js +0 -7
- package/dist/oracle/src/oracle/oscProgress.js +0 -60
- package/dist/oracle/src/oracle/request.js +0 -48
- package/dist/oracle/src/oracle/run.js +0 -444
- package/dist/oracle/src/oracle/tokenStats.js +0 -39
- package/dist/oracle/src/oracle/types.js +0 -1
- package/dist/oracle/src/oracle.js +0 -9
- package/dist/oracle/src/sessionManager.js +0 -205
- package/dist/oracle/src/version.js +0 -39
- package/dist/scripts/chrome/browser-tools.js +0 -295
- package/dist/src/browser/profileSync.js +0 -141
- /package/dist/{oracle/src/browser/types.js → src/gemini-web/executionClients.js} +0 -0
|
@@ -1,35 +1,37 @@
|
|
|
1
|
-
import { MENU_CONTAINER_SELECTOR, MENU_ITEM_SELECTOR } from
|
|
2
|
-
import { logDomFailure } from
|
|
3
|
-
import { buildClickDispatcher } from
|
|
1
|
+
import { MENU_CONTAINER_SELECTOR, MENU_ITEM_SELECTOR, MODEL_BUTTON_SELECTOR, } from "../constants.js";
|
|
2
|
+
import { logDomFailure } from "../domDebug.js";
|
|
3
|
+
import { buildClickDispatcher } from "./domEvents.js";
|
|
4
4
|
/**
|
|
5
|
-
* Selects a specific thinking time level in ChatGPT's composer
|
|
5
|
+
* Selects a specific thinking time level in ChatGPT's composer.
|
|
6
|
+
*
|
|
7
|
+
* Best-effort: if the chip / menu / option is missing (e.g. ChatGPT moved the
|
|
8
|
+
* effort selector into the per-model trailing button and we can't navigate it,
|
|
9
|
+
* or the language pack uses tokens we don't yet match), we log a debug dump
|
|
10
|
+
* and continue with whatever effort the UI defaults to.
|
|
11
|
+
*
|
|
6
12
|
* @param level - The thinking time intensity: 'light', 'standard', 'extended', or 'heavy'
|
|
7
13
|
*/
|
|
8
14
|
export async function ensureThinkingTime(Runtime, level, logger) {
|
|
9
15
|
const result = await evaluateThinkingTimeSelection(Runtime, level);
|
|
10
16
|
const capitalizedLevel = level.charAt(0).toUpperCase() + level.slice(1);
|
|
11
17
|
switch (result?.status) {
|
|
12
|
-
case
|
|
18
|
+
case "already-selected":
|
|
13
19
|
logger(`Thinking time: ${result.label ?? capitalizedLevel} (already selected)`);
|
|
14
20
|
return;
|
|
15
|
-
case
|
|
21
|
+
case "switched":
|
|
16
22
|
logger(`Thinking time: ${result.label ?? capitalizedLevel}`);
|
|
17
23
|
return;
|
|
18
|
-
case
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
throw new Error('Unable to find the Thinking time dropdown menu.');
|
|
25
|
-
}
|
|
26
|
-
case 'option-not-found': {
|
|
27
|
-
await logDomFailure(Runtime, logger, `${level}-option`);
|
|
28
|
-
throw new Error(`Unable to find the ${capitalizedLevel} option in the Thinking time menu.`);
|
|
24
|
+
case "chip-not-found":
|
|
25
|
+
case "menu-not-found":
|
|
26
|
+
case "option-not-found": {
|
|
27
|
+
await logDomFailure(Runtime, logger, `thinking-${result.status}`);
|
|
28
|
+
logger(`Thinking time: ${result.status.replaceAll("-", " ")} (requested ${capitalizedLevel}); continuing with ChatGPT default.`);
|
|
29
|
+
return;
|
|
29
30
|
}
|
|
30
31
|
default: {
|
|
31
|
-
await logDomFailure(Runtime, logger,
|
|
32
|
-
|
|
32
|
+
await logDomFailure(Runtime, logger, "thinking-time-unknown");
|
|
33
|
+
logger(`Thinking time: unknown outcome selecting ${capitalizedLevel}; continuing with ChatGPT default.`);
|
|
34
|
+
return;
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
37
|
}
|
|
@@ -43,22 +45,22 @@ export async function ensureThinkingTimeIfAvailable(Runtime, level, logger) {
|
|
|
43
45
|
const result = await evaluateThinkingTimeSelection(Runtime, level);
|
|
44
46
|
const capitalizedLevel = level.charAt(0).toUpperCase() + level.slice(1);
|
|
45
47
|
switch (result?.status) {
|
|
46
|
-
case
|
|
48
|
+
case "already-selected":
|
|
47
49
|
logger(`Thinking time: ${result.label ?? capitalizedLevel} (already selected)`);
|
|
48
50
|
return true;
|
|
49
|
-
case
|
|
51
|
+
case "switched":
|
|
50
52
|
logger(`Thinking time: ${result.label ?? capitalizedLevel}`);
|
|
51
53
|
return true;
|
|
52
|
-
case
|
|
53
|
-
case
|
|
54
|
-
case
|
|
54
|
+
case "chip-not-found":
|
|
55
|
+
case "menu-not-found":
|
|
56
|
+
case "option-not-found":
|
|
55
57
|
if (logger.verbose) {
|
|
56
|
-
logger(`Thinking time: ${result.status.replaceAll(
|
|
58
|
+
logger(`Thinking time: ${result.status.replaceAll("-", " ")}; continuing with default.`);
|
|
57
59
|
}
|
|
58
60
|
return false;
|
|
59
61
|
default:
|
|
60
62
|
if (logger.verbose) {
|
|
61
|
-
logger(
|
|
63
|
+
logger("Thinking time: unknown outcome; continuing with default.");
|
|
62
64
|
}
|
|
63
65
|
return false;
|
|
64
66
|
}
|
|
@@ -67,7 +69,7 @@ export async function ensureThinkingTimeIfAvailable(Runtime, level, logger) {
|
|
|
67
69
|
const message = error instanceof Error ? error.message : String(error);
|
|
68
70
|
if (logger.verbose) {
|
|
69
71
|
logger(`Thinking time selection failed (${message}); continuing with default.`);
|
|
70
|
-
await logDomFailure(Runtime, logger,
|
|
72
|
+
await logDomFailure(Runtime, logger, "thinking-time");
|
|
71
73
|
}
|
|
72
74
|
return false;
|
|
73
75
|
}
|
|
@@ -83,124 +85,219 @@ async function evaluateThinkingTimeSelection(Runtime, level) {
|
|
|
83
85
|
function buildThinkingTimeExpression(level) {
|
|
84
86
|
const menuContainerLiteral = JSON.stringify(MENU_CONTAINER_SELECTOR);
|
|
85
87
|
const menuItemLiteral = JSON.stringify(MENU_ITEM_SELECTOR);
|
|
88
|
+
const modelButtonLiteral = JSON.stringify(MODEL_BUTTON_SELECTOR);
|
|
86
89
|
const targetLevelLiteral = JSON.stringify(level.toLowerCase());
|
|
87
90
|
return `(async () => {
|
|
88
91
|
${buildClickDispatcher()}
|
|
89
92
|
|
|
90
93
|
const MENU_CONTAINER_SELECTOR = ${menuContainerLiteral};
|
|
91
94
|
const MENU_ITEM_SELECTOR = ${menuItemLiteral};
|
|
95
|
+
const MODEL_BUTTON_SELECTOR = ${modelButtonLiteral};
|
|
92
96
|
const TARGET_LEVEL = ${targetLevelLiteral};
|
|
93
97
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
// Bilingual matchers: English level token + observed Chinese variants.
|
|
99
|
+
const LEVEL_TOKENS = {
|
|
100
|
+
light: ['light', '轻'],
|
|
101
|
+
standard: ['standard', '标准'],
|
|
102
|
+
extended: ['extended', '扩展', '深度', '加强'],
|
|
103
|
+
heavy: ['heavy', '重度', '加重', '高'],
|
|
104
|
+
};
|
|
105
|
+
const targetTokens = LEVEL_TOKENS[TARGET_LEVEL] || [TARGET_LEVEL];
|
|
99
106
|
|
|
100
107
|
const INITIAL_WAIT_MS = 150;
|
|
101
|
-
const
|
|
108
|
+
const STEP_WAIT_MS = 200;
|
|
109
|
+
const MAX_WAIT_MS = 8000;
|
|
102
110
|
|
|
111
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
112
|
+
// Keep CJK characters so we can match Chinese labels against LEVEL_TOKENS.
|
|
103
113
|
const normalize = (value) => (value || '')
|
|
104
114
|
.toLowerCase()
|
|
105
|
-
.replace(/[^a-z0-9]+/g, ' ')
|
|
115
|
+
.replace(/[^a-z0-9\\u4e00-\\u9fa5]+/g, ' ')
|
|
106
116
|
.replace(/\\s+/g, ' ')
|
|
107
117
|
.trim();
|
|
118
|
+
const matchesLevel = (text) => {
|
|
119
|
+
const t = normalize(text);
|
|
120
|
+
return targetTokens.some((tok) => t.includes(String(tok).toLowerCase()));
|
|
121
|
+
};
|
|
122
|
+
const optionIsSelected = (node) => {
|
|
123
|
+
if (!(node instanceof HTMLElement)) return false;
|
|
124
|
+
const ariaChecked = node.getAttribute('aria-checked');
|
|
125
|
+
const dataState = (node.getAttribute('data-state') || '').toLowerCase();
|
|
126
|
+
if (ariaChecked === 'true') return true;
|
|
127
|
+
return dataState === 'checked' || dataState === 'selected' || dataState === 'on';
|
|
128
|
+
};
|
|
129
|
+
const closeOpenMenus = () => {
|
|
130
|
+
try {
|
|
131
|
+
document.dispatchEvent(
|
|
132
|
+
new KeyboardEvent('keydown', { key: 'Escape', code: 'Escape', keyCode: 27, which: 27, bubbles: true }),
|
|
133
|
+
);
|
|
134
|
+
} catch {}
|
|
135
|
+
};
|
|
108
136
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
137
|
+
// ---------- OLD UI: standalone composer chip labelled "Thinking" ----------
|
|
138
|
+
const OLD_CHIP_SELECTORS = [
|
|
139
|
+
'[data-testid="composer-footer-actions"] button[aria-haspopup="menu"]',
|
|
140
|
+
'.__composer-pill-composite button[aria-haspopup="menu"]',
|
|
141
|
+
];
|
|
142
|
+
const findOldChip = () => {
|
|
143
|
+
for (const selector of OLD_CHIP_SELECTORS) {
|
|
144
|
+
for (const btn of document.querySelectorAll(selector)) {
|
|
114
145
|
if (btn.getAttribute?.('aria-haspopup') !== 'menu') continue;
|
|
146
|
+
// The new model picker pill also reuses .__composer-pill — skip it.
|
|
147
|
+
if (btn.matches?.(MODEL_BUTTON_SELECTOR)) continue;
|
|
115
148
|
const aria = normalize(btn.getAttribute?.('aria-label') ?? '');
|
|
116
149
|
const text = normalize(btn.textContent ?? '');
|
|
117
|
-
if (aria.includes('thinking') || text.includes('thinking'))
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
150
|
+
if (aria.includes('thinking') || text.includes('thinking')) return btn;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
};
|
|
155
|
+
const findOldEffortMenu = () => {
|
|
156
|
+
const menus = document.querySelectorAll(MENU_CONTAINER_SELECTOR + ', [role="group"]');
|
|
157
|
+
for (const menu of menus) {
|
|
158
|
+
const label = menu.querySelector?.('.__menu-label, [class*="menu-label"]');
|
|
159
|
+
if (normalize(label?.textContent ?? '').includes('thinking time')) return menu;
|
|
160
|
+
const text = normalize(menu.textContent ?? '');
|
|
161
|
+
if (text.includes('standard') && text.includes('extended')) return menu;
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
};
|
|
165
|
+
const findOptionInMenu = (menu) => {
|
|
166
|
+
for (const item of menu.querySelectorAll(MENU_ITEM_SELECTOR)) {
|
|
167
|
+
if (
|
|
168
|
+
matchesLevel(item.textContent ?? '') ||
|
|
169
|
+
matchesLevel(item.getAttribute?.('aria-label') ?? '')
|
|
170
|
+
) {
|
|
171
|
+
return item;
|
|
125
172
|
}
|
|
126
173
|
}
|
|
127
174
|
return null;
|
|
128
175
|
};
|
|
129
176
|
|
|
130
|
-
const
|
|
131
|
-
if (
|
|
177
|
+
const oldChip = findOldChip();
|
|
178
|
+
if (oldChip) {
|
|
179
|
+
dispatchClickSequence(oldChip);
|
|
180
|
+
const start = performance.now();
|
|
181
|
+
while (performance.now() - start < MAX_WAIT_MS) {
|
|
182
|
+
await sleep(100);
|
|
183
|
+
const menu = findOldEffortMenu();
|
|
184
|
+
if (!menu) continue;
|
|
185
|
+
const opt = findOptionInMenu(menu);
|
|
186
|
+
if (!opt) {
|
|
187
|
+
closeOpenMenus();
|
|
188
|
+
return { status: 'option-not-found' };
|
|
189
|
+
}
|
|
190
|
+
const already = optionIsSelected(opt);
|
|
191
|
+
const label = opt.textContent?.trim?.() || null;
|
|
192
|
+
dispatchClickSequence(opt);
|
|
193
|
+
await sleep(STEP_WAIT_MS);
|
|
194
|
+
closeOpenMenus();
|
|
195
|
+
return { status: already ? 'already-selected' : 'switched', label };
|
|
196
|
+
}
|
|
197
|
+
closeOpenMenus();
|
|
198
|
+
return { status: 'menu-not-found' };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ---------- NEW UI: thinking effort lives inside the model picker ----------
|
|
202
|
+
// Each eligible model row carries a trailing button:
|
|
203
|
+
// [data-model-picker-thinking-effort-action="true"] (role="menuitem", aria-haspopup="menu")
|
|
204
|
+
// Clicking it expands a submenu of effort options. We use aria-controls to
|
|
205
|
+
// resolve the submenu deterministically rather than scoring menu contents.
|
|
206
|
+
const TRAILING_SELECTOR = '[data-model-picker-thinking-effort-action="true"]';
|
|
207
|
+
|
|
208
|
+
const findModelButton = () => document.querySelector(MODEL_BUTTON_SELECTOR);
|
|
209
|
+
const findTrailingButtons = () => Array.from(document.querySelectorAll(TRAILING_SELECTOR));
|
|
210
|
+
const pickTrailingForCurrentModel = () => {
|
|
211
|
+
const trailings = findTrailingButtons();
|
|
212
|
+
if (trailings.length === 0) return null;
|
|
213
|
+
if (trailings.length === 1) return trailings[0];
|
|
214
|
+
// Prefer the trailing button whose model row is currently selected.
|
|
215
|
+
for (const t of trailings) {
|
|
216
|
+
const row = t.closest('[role="menuitem"], [role="menuitemradio"], [data-radix-collection-item]');
|
|
217
|
+
if (row && (optionIsSelected(row) || row.querySelector('[aria-checked="true"]'))) return t;
|
|
218
|
+
}
|
|
219
|
+
// Fallback: first one with non-zero box.
|
|
220
|
+
for (const t of trailings) {
|
|
221
|
+
const r = t.getBoundingClientRect?.();
|
|
222
|
+
if (r && r.width > 0 && r.height > 0) return t;
|
|
223
|
+
}
|
|
224
|
+
return trailings[0];
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const modelBtn = findModelButton();
|
|
228
|
+
if (!modelBtn) {
|
|
132
229
|
return { status: 'chip-not-found' };
|
|
133
230
|
}
|
|
134
231
|
|
|
135
|
-
|
|
232
|
+
// Open model menu (idempotent — leaves it open if already open).
|
|
233
|
+
if (modelBtn.getAttribute('aria-expanded') !== 'true') {
|
|
234
|
+
dispatchClickSequence(modelBtn);
|
|
235
|
+
await sleep(INITIAL_WAIT_MS);
|
|
236
|
+
}
|
|
136
237
|
|
|
137
|
-
|
|
138
|
-
|
|
238
|
+
let trailing = null;
|
|
239
|
+
const trailingDeadline = performance.now() + MAX_WAIT_MS;
|
|
240
|
+
while (performance.now() < trailingDeadline) {
|
|
241
|
+
trailing = pickTrailingForCurrentModel();
|
|
242
|
+
if (trailing) break;
|
|
243
|
+
await sleep(100);
|
|
244
|
+
}
|
|
245
|
+
if (!trailing) {
|
|
246
|
+
closeOpenMenus();
|
|
247
|
+
return { status: 'chip-not-found' };
|
|
248
|
+
}
|
|
139
249
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
for (const menu of menus) {
|
|
143
|
-
const label = menu.querySelector?.('.__menu-label, [class*="menu-label"]');
|
|
144
|
-
if (normalize(label?.textContent ?? '').includes('thinking time')) {
|
|
145
|
-
return menu;
|
|
146
|
-
}
|
|
147
|
-
const text = normalize(menu.textContent ?? '');
|
|
148
|
-
if (text.includes('standard') && text.includes('extended')) {
|
|
149
|
-
return menu;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return null;
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
const findTargetOption = (menu) => {
|
|
156
|
-
const items = menu.querySelectorAll(MENU_ITEM_SELECTOR);
|
|
157
|
-
for (const item of items) {
|
|
158
|
-
const text = normalize(item.textContent ?? '');
|
|
159
|
-
if (text.includes(TARGET_LEVEL)) {
|
|
160
|
-
return item;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
return null;
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
const optionIsSelected = (node) => {
|
|
167
|
-
if (!(node instanceof HTMLElement)) return false;
|
|
168
|
-
const ariaChecked = node.getAttribute('aria-checked');
|
|
169
|
-
const dataState = (node.getAttribute('data-state') || '').toLowerCase();
|
|
170
|
-
if (ariaChecked === 'true') return true;
|
|
171
|
-
if (dataState === 'checked' || dataState === 'selected' || dataState === 'on') return true;
|
|
172
|
-
return false;
|
|
173
|
-
};
|
|
250
|
+
dispatchClickSequence(trailing);
|
|
251
|
+
await sleep(STEP_WAIT_MS);
|
|
174
252
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
253
|
+
// Resolve the effort submenu via aria-controls when ChatGPT exposes it,
|
|
254
|
+
// otherwise fall back to scanning newly opened menus for our level tokens.
|
|
255
|
+
const resolveEffortMenu = () => {
|
|
256
|
+
const id = trailing.getAttribute('aria-controls');
|
|
257
|
+
if (id) {
|
|
258
|
+
const node = document.getElementById(id);
|
|
259
|
+
if (node) return node;
|
|
260
|
+
}
|
|
261
|
+
const menus = document.querySelectorAll(MENU_CONTAINER_SELECTOR + ', [role="group"]');
|
|
262
|
+
let best = null;
|
|
263
|
+
for (const menu of menus) {
|
|
264
|
+
if (menu === modelBtn || menu.contains(trailing)) continue;
|
|
265
|
+
const text = normalize(menu.textContent ?? '');
|
|
266
|
+
let hits = 0;
|
|
267
|
+
for (const tokens of Object.values(LEVEL_TOKENS)) {
|
|
268
|
+
if (tokens.some((tok) => text.includes(String(tok).toLowerCase()))) hits += 1;
|
|
184
269
|
}
|
|
270
|
+
if (hits >= 2 && (!best || hits > best.hits)) best = { menu, hits };
|
|
271
|
+
}
|
|
272
|
+
return best?.menu ?? null;
|
|
273
|
+
};
|
|
185
274
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
275
|
+
let effortMenu = null;
|
|
276
|
+
const effortDeadline = performance.now() + MAX_WAIT_MS;
|
|
277
|
+
while (performance.now() < effortDeadline) {
|
|
278
|
+
effortMenu = resolveEffortMenu();
|
|
279
|
+
if (effortMenu) break;
|
|
280
|
+
await sleep(100);
|
|
281
|
+
}
|
|
282
|
+
if (!effortMenu) {
|
|
283
|
+
closeOpenMenus();
|
|
284
|
+
return { status: 'menu-not-found' };
|
|
285
|
+
}
|
|
191
286
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
resolve({ status: alreadySelected ? 'already-selected' : 'switched', label });
|
|
198
|
-
};
|
|
287
|
+
const targetOption = findOptionInMenu(effortMenu);
|
|
288
|
+
if (!targetOption) {
|
|
289
|
+
closeOpenMenus();
|
|
290
|
+
return { status: 'option-not-found' };
|
|
291
|
+
}
|
|
199
292
|
|
|
200
|
-
|
|
201
|
-
|
|
293
|
+
const already = optionIsSelected(targetOption);
|
|
294
|
+
const label = targetOption.textContent?.trim?.() || null;
|
|
295
|
+
dispatchClickSequence(targetOption);
|
|
296
|
+
await sleep(STEP_WAIT_MS);
|
|
297
|
+
closeOpenMenus();
|
|
298
|
+
return { status: already ? 'already-selected' : 'switched', label };
|
|
202
299
|
})()`;
|
|
203
300
|
}
|
|
204
|
-
export function buildThinkingTimeExpressionForTest(level =
|
|
301
|
+
export function buildThinkingTimeExpressionForTest(level = "extended") {
|
|
205
302
|
return buildThinkingTimeExpression(level);
|
|
206
303
|
}
|