@steipete/oracle 0.9.0 → 0.11.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.
Files changed (194) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +107 -49
  3. package/dist/bin/oracle-cli.js +551 -410
  4. package/dist/bin/oracle-mcp.js +2 -2
  5. package/dist/bin/oracle.js +165 -279
  6. package/dist/scripts/agent-send.js +31 -31
  7. package/dist/scripts/check.js +6 -6
  8. package/dist/scripts/debug/extract-chatgpt-response.js +10 -10
  9. package/dist/scripts/docs-list.js +30 -30
  10. package/dist/scripts/git-policy.js +25 -23
  11. package/dist/scripts/run-cli.js +8 -8
  12. package/dist/scripts/runner.js +203 -195
  13. package/dist/scripts/test-browser.js +21 -18
  14. package/dist/scripts/test-remote-chrome.js +20 -20
  15. package/dist/src/bridge/connection.js +18 -18
  16. package/dist/src/bridge/userConfigFile.js +7 -7
  17. package/dist/src/browser/actions/archiveConversation.js +224 -0
  18. package/dist/src/browser/actions/assistantResponse.js +175 -101
  19. package/dist/src/browser/actions/attachmentDataTransfer.js +49 -47
  20. package/dist/src/browser/actions/attachments.js +246 -150
  21. package/dist/src/browser/actions/deepResearch.js +662 -0
  22. package/dist/src/browser/actions/domEvents.js +2 -2
  23. package/dist/src/browser/actions/modelSelection.js +342 -119
  24. package/dist/src/browser/actions/navigation.js +183 -137
  25. package/dist/src/browser/actions/projectSources.js +491 -0
  26. package/dist/src/browser/actions/promptComposer.js +152 -91
  27. package/dist/src/browser/actions/remoteFileTransfer.js +10 -10
  28. package/dist/src/browser/actions/thinkingStatus.js +391 -0
  29. package/dist/src/browser/actions/thinkingTime.js +207 -110
  30. package/dist/src/browser/artifacts.js +150 -0
  31. package/dist/src/browser/attachRunning.js +31 -0
  32. package/dist/src/browser/chatgptImages.js +315 -0
  33. package/dist/src/browser/chromeLifecycle.js +276 -63
  34. package/dist/src/browser/config.js +59 -16
  35. package/dist/src/browser/constants.js +25 -12
  36. package/dist/src/browser/controlPlan.js +81 -0
  37. package/dist/src/browser/cookies.js +19 -19
  38. package/dist/src/browser/detect.js +250 -77
  39. package/dist/src/browser/domDebug.js +50 -1
  40. package/dist/src/browser/index.js +1559 -692
  41. package/dist/src/browser/liveTabs.js +434 -0
  42. package/dist/src/browser/modelStrategy.js +1 -1
  43. package/dist/src/browser/pageActions.js +5 -5
  44. package/dist/src/browser/policies.js +16 -13
  45. package/dist/src/browser/profileState.js +127 -42
  46. package/dist/src/browser/projectSourcesRunner.js +366 -0
  47. package/dist/src/browser/prompt.js +72 -42
  48. package/dist/src/browser/promptSummary.js +5 -5
  49. package/dist/src/browser/providerDomFlow.js +1 -1
  50. package/dist/src/browser/providers/chatgptDomProvider.js +9 -9
  51. package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +51 -42
  52. package/dist/src/browser/providers/index.js +2 -2
  53. package/dist/src/browser/reattach.js +178 -73
  54. package/dist/src/browser/reattachHelpers.js +32 -27
  55. package/dist/src/browser/sessionRunner.js +89 -25
  56. package/dist/src/browser/tabLeaseRegistry.js +182 -0
  57. package/dist/src/browser/utils.js +9 -9
  58. package/dist/src/browserMode.js +1 -1
  59. package/dist/src/cli/bridge/claudeConfig.js +24 -20
  60. package/dist/src/cli/bridge/client.js +28 -20
  61. package/dist/src/cli/bridge/codexConfig.js +16 -16
  62. package/dist/src/cli/bridge/doctor.js +47 -39
  63. package/dist/src/cli/bridge/host.js +58 -56
  64. package/dist/src/cli/browserConfig.js +102 -48
  65. package/dist/src/cli/browserDefaults.js +51 -26
  66. package/dist/src/cli/browserTabs.js +228 -0
  67. package/dist/src/cli/bundleWarnings.js +1 -1
  68. package/dist/src/cli/clipboard.js +11 -2
  69. package/dist/src/cli/detach.js +2 -2
  70. package/dist/src/cli/dryRun.js +62 -26
  71. package/dist/src/cli/duplicatePromptGuard.js +12 -4
  72. package/dist/src/cli/engine.js +9 -9
  73. package/dist/src/cli/errorUtils.js +1 -1
  74. package/dist/src/cli/fileSize.js +3 -3
  75. package/dist/src/cli/format.js +2 -2
  76. package/dist/src/cli/help.js +28 -28
  77. package/dist/src/cli/hiddenAliases.js +3 -3
  78. package/dist/src/cli/markdownBundle.js +7 -7
  79. package/dist/src/cli/markdownRenderer.js +15 -15
  80. package/dist/src/cli/notifier.js +77 -67
  81. package/dist/src/cli/options.js +131 -106
  82. package/dist/src/cli/oscUtils.js +1 -1
  83. package/dist/src/cli/projectSources.js +116 -0
  84. package/dist/src/cli/promptRequirement.js +2 -2
  85. package/dist/src/cli/renderOutput.js +1 -1
  86. package/dist/src/cli/rootAlias.js +1 -1
  87. package/dist/src/cli/runOptions.js +32 -28
  88. package/dist/src/cli/sessionCommand.js +82 -21
  89. package/dist/src/cli/sessionDisplay.js +213 -87
  90. package/dist/src/cli/sessionLineage.js +6 -2
  91. package/dist/src/cli/sessionRunner.js +149 -95
  92. package/dist/src/cli/sessionTable.js +26 -23
  93. package/dist/src/cli/stdin.js +22 -0
  94. package/dist/src/cli/tagline.js +121 -124
  95. package/dist/src/cli/tui/index.js +139 -128
  96. package/dist/src/cli/writeOutputPath.js +5 -5
  97. package/dist/src/config.js +7 -7
  98. package/dist/src/gemini-web/browserSessionManager.js +19 -15
  99. package/dist/src/gemini-web/client.js +76 -70
  100. package/dist/src/gemini-web/executionMode.js +6 -8
  101. package/dist/src/gemini-web/executor.js +98 -93
  102. package/dist/src/gemini-web/index.js +1 -1
  103. package/dist/src/mcp/consultPresets.js +19 -0
  104. package/dist/src/mcp/server.js +18 -12
  105. package/dist/src/mcp/tools/consult.js +246 -67
  106. package/dist/src/mcp/tools/projectSources.js +123 -0
  107. package/dist/src/mcp/tools/sessionResources.js +12 -12
  108. package/dist/src/mcp/tools/sessions.js +26 -17
  109. package/dist/src/mcp/types.js +12 -5
  110. package/dist/src/mcp/utils.js +21 -8
  111. package/dist/src/oracle/background.js +15 -15
  112. package/dist/src/oracle/claude.js +53 -25
  113. package/dist/src/oracle/client.js +50 -41
  114. package/dist/src/oracle/config.js +96 -66
  115. package/dist/src/oracle/errors.js +38 -38
  116. package/dist/src/oracle/files.js +55 -46
  117. package/dist/src/oracle/finishLine.js +10 -8
  118. package/dist/src/oracle/format.js +3 -3
  119. package/dist/src/oracle/gemini.js +37 -33
  120. package/dist/src/oracle/logging.js +7 -7
  121. package/dist/src/oracle/markdown.js +28 -28
  122. package/dist/src/oracle/modelResolver.js +16 -16
  123. package/dist/src/oracle/multiModelRunner.js +12 -12
  124. package/dist/src/oracle/oscProgress.js +8 -8
  125. package/dist/src/oracle/promptAssembly.js +6 -3
  126. package/dist/src/oracle/request.js +16 -13
  127. package/dist/src/oracle/run.js +160 -135
  128. package/dist/src/oracle/runUtils.js +8 -5
  129. package/dist/src/oracle/tokenEstimate.js +6 -6
  130. package/dist/src/oracle/tokenStats.js +5 -5
  131. package/dist/src/oracle/tokenStringifier.js +5 -5
  132. package/dist/src/oracle.js +12 -12
  133. package/dist/src/oracleHome.js +3 -3
  134. package/dist/src/projectSources/plan.js +27 -0
  135. package/dist/src/projectSources/url.js +23 -0
  136. package/dist/src/remote/client.js +25 -25
  137. package/dist/src/remote/health.js +20 -20
  138. package/dist/src/remote/remoteServiceConfig.js +9 -9
  139. package/dist/src/remote/server.js +129 -118
  140. package/dist/src/sessionManager.js +78 -75
  141. package/dist/src/sessionStore.js +3 -3
  142. package/dist/src/version.js +10 -10
  143. package/dist/vendor/oracle-notifier/README.md +2 -0
  144. package/package.json +67 -62
  145. package/vendor/oracle-notifier/README.md +2 -0
  146. package/dist/markdansi/types/index.js +0 -4
  147. package/dist/oracle/bin/oracle-cli.js +0 -472
  148. package/dist/oracle/src/browser/actions/assistantResponse.js +0 -471
  149. package/dist/oracle/src/browser/actions/attachments.js +0 -82
  150. package/dist/oracle/src/browser/actions/modelSelection.js +0 -190
  151. package/dist/oracle/src/browser/actions/navigation.js +0 -75
  152. package/dist/oracle/src/browser/actions/promptComposer.js +0 -167
  153. package/dist/oracle/src/browser/chromeLifecycle.js +0 -104
  154. package/dist/oracle/src/browser/config.js +0 -33
  155. package/dist/oracle/src/browser/constants.js +0 -40
  156. package/dist/oracle/src/browser/cookies.js +0 -210
  157. package/dist/oracle/src/browser/domDebug.js +0 -36
  158. package/dist/oracle/src/browser/index.js +0 -331
  159. package/dist/oracle/src/browser/pageActions.js +0 -5
  160. package/dist/oracle/src/browser/prompt.js +0 -88
  161. package/dist/oracle/src/browser/promptSummary.js +0 -20
  162. package/dist/oracle/src/browser/sessionRunner.js +0 -80
  163. package/dist/oracle/src/browser/utils.js +0 -62
  164. package/dist/oracle/src/browserMode.js +0 -1
  165. package/dist/oracle/src/cli/browserConfig.js +0 -44
  166. package/dist/oracle/src/cli/dryRun.js +0 -59
  167. package/dist/oracle/src/cli/engine.js +0 -17
  168. package/dist/oracle/src/cli/errorUtils.js +0 -9
  169. package/dist/oracle/src/cli/help.js +0 -70
  170. package/dist/oracle/src/cli/markdownRenderer.js +0 -15
  171. package/dist/oracle/src/cli/options.js +0 -103
  172. package/dist/oracle/src/cli/promptRequirement.js +0 -14
  173. package/dist/oracle/src/cli/rootAlias.js +0 -30
  174. package/dist/oracle/src/cli/sessionCommand.js +0 -77
  175. package/dist/oracle/src/cli/sessionDisplay.js +0 -270
  176. package/dist/oracle/src/cli/sessionRunner.js +0 -94
  177. package/dist/oracle/src/heartbeat.js +0 -43
  178. package/dist/oracle/src/oracle/client.js +0 -48
  179. package/dist/oracle/src/oracle/config.js +0 -29
  180. package/dist/oracle/src/oracle/errors.js +0 -101
  181. package/dist/oracle/src/oracle/files.js +0 -220
  182. package/dist/oracle/src/oracle/format.js +0 -33
  183. package/dist/oracle/src/oracle/fsAdapter.js +0 -7
  184. package/dist/oracle/src/oracle/oscProgress.js +0 -60
  185. package/dist/oracle/src/oracle/request.js +0 -48
  186. package/dist/oracle/src/oracle/run.js +0 -444
  187. package/dist/oracle/src/oracle/tokenStats.js +0 -39
  188. package/dist/oracle/src/oracle/types.js +0 -1
  189. package/dist/oracle/src/oracle.js +0 -9
  190. package/dist/oracle/src/sessionManager.js +0 -205
  191. package/dist/oracle/src/version.js +0 -39
  192. package/dist/scripts/chrome/browser-tools.js +0 -295
  193. package/dist/src/browser/profileSync.js +0 -141
  194. /package/dist/{oracle/src/browser → src/projectSources}/types.js +0 -0
@@ -0,0 +1,391 @@
1
+ import { formatElapsed } from "../../oracle/format.js";
2
+ import { ASSISTANT_ROLE_SELECTOR, CONVERSATION_TURN_SELECTOR } from "../constants.js";
3
+ const THINKING_STALE_HINT_MS = 10 * 60_000;
4
+ export function startThinkingStatusMonitor(Runtime, logger, options = {}) {
5
+ const intervalMs = resolveThinkingStatusInterval(options.intervalMs);
6
+ if (!intervalMs) {
7
+ return () => { };
8
+ }
9
+ const now = options.now ?? Date.now;
10
+ let stopped = false;
11
+ let pending = false;
12
+ let lastFingerprint = null;
13
+ let lastChangedAt = now();
14
+ const startedAt = now();
15
+ const interval = setInterval(async () => {
16
+ // stop flag flips asynchronously
17
+ if (stopped || pending) {
18
+ return;
19
+ }
20
+ pending = true;
21
+ try {
22
+ const snapshot = await readThinkingStatus(Runtime);
23
+ if (stopped) {
24
+ return;
25
+ }
26
+ const tickAt = now();
27
+ if (snapshot) {
28
+ const fingerprint = buildThinkingStatusFingerprint(snapshot);
29
+ if (fingerprint !== lastFingerprint) {
30
+ lastFingerprint = fingerprint;
31
+ lastChangedAt = tickAt;
32
+ }
33
+ if (stopped) {
34
+ return;
35
+ }
36
+ logger(formatThinkingLog(startedAt, tickAt, snapshot, "", tickAt - lastChangedAt));
37
+ }
38
+ else {
39
+ logger(formatThinkingWaitingLog(startedAt, tickAt));
40
+ }
41
+ }
42
+ catch {
43
+ // ignore DOM polling errors
44
+ }
45
+ finally {
46
+ pending = false;
47
+ }
48
+ }, intervalMs);
49
+ interval.unref?.();
50
+ return () => {
51
+ // multiple callers may race to stop
52
+ if (stopped) {
53
+ return;
54
+ }
55
+ stopped = true;
56
+ clearInterval(interval);
57
+ };
58
+ }
59
+ export function formatThinkingLog(startedAt, now, status, locatorSuffix, unchangedMs = 0) {
60
+ const elapsedMs = now - startedAt;
61
+ const elapsedText = formatElapsed(elapsedMs);
62
+ const snapshot = typeof status === "string"
63
+ ? { message: sanitizeThinkingText(status) || "active", source: "inline" }
64
+ : status;
65
+ const progress = typeof snapshot.progressPercent === "number" && Number.isFinite(snapshot.progressPercent)
66
+ ? `${Math.max(0, Math.min(100, Math.round(snapshot.progressPercent)))}% UI progress`
67
+ : null;
68
+ const prefix = progress
69
+ ? `[browser] ChatGPT thinking - ${progress}, ${elapsedText} elapsed`
70
+ : `[browser] ChatGPT thinking - ${elapsedText} elapsed`;
71
+ const statusLabel = snapshot.message ? `; status=${snapshot.message}` : "";
72
+ const changeLabel = unchangedMs > 0 ? `; last change ${formatElapsed(unchangedMs)} ago` : "";
73
+ const staleLabel = unchangedMs >= THINKING_STALE_HINT_MS ? "; stale-hint=no UI progress change" : "";
74
+ const sourceLabel = snapshot.source ? `; source=${snapshot.source}` : "";
75
+ return `${prefix}${statusLabel}${changeLabel}${staleLabel}${sourceLabel}${locatorSuffix}`;
76
+ }
77
+ export function formatThinkingWaitingLog(startedAt, now) {
78
+ return `[browser] Waiting for ChatGPT response - ${formatElapsed(now - startedAt)} elapsed; no thinking status detected yet.`;
79
+ }
80
+ function resolveThinkingStatusInterval(intervalMs) {
81
+ if (intervalMs === 0) {
82
+ return null;
83
+ }
84
+ if (typeof intervalMs === "number" && Number.isFinite(intervalMs) && intervalMs > 0) {
85
+ return Math.max(1000, Math.floor(intervalMs));
86
+ }
87
+ return 30_000;
88
+ }
89
+ function buildThinkingStatusFingerprint(snapshot) {
90
+ return [
91
+ snapshot.source,
92
+ snapshot.message,
93
+ snapshot.progressPercent == null ? "" : Math.round(snapshot.progressPercent),
94
+ snapshot.panelVisible ? "panel" : "",
95
+ ].join(":");
96
+ }
97
+ async function readThinkingStatus(Runtime) {
98
+ const expression = buildThinkingStatusExpression();
99
+ const { result } = await Runtime.evaluate({
100
+ expression,
101
+ awaitPromise: true,
102
+ returnByValue: true,
103
+ });
104
+ const value = result.value;
105
+ if (typeof value === "string") {
106
+ const sanitized = sanitizeThinkingText(value);
107
+ return sanitized ? { message: sanitized, source: "inline" } : null;
108
+ }
109
+ if (!value || typeof value !== "object") {
110
+ return null;
111
+ }
112
+ const source = value.source === "sidecar" ? "sidecar" : "inline";
113
+ const message = sanitizeThinkingText(value.message ?? "");
114
+ const progressPercent = typeof value.progressPercent === "number" && Number.isFinite(value.progressPercent)
115
+ ? Math.max(0, Math.min(100, value.progressPercent))
116
+ : undefined;
117
+ if (!message && progressPercent == null) {
118
+ return null;
119
+ }
120
+ return {
121
+ message: message || "active",
122
+ source,
123
+ progressPercent,
124
+ panelOpened: value.panelOpened === true,
125
+ panelVisible: value.panelVisible === true,
126
+ };
127
+ }
128
+ const SAFE_THINKING_STATUS_MESSAGES = new Set([
129
+ "active",
130
+ "thinking sidecar active",
131
+ "thinking sidecar opened",
132
+ ]);
133
+ export function sanitizeThinkingText(raw) {
134
+ if (!raw) {
135
+ return "";
136
+ }
137
+ const trimmed = raw.replace(/\s+/g, " ").trim();
138
+ const prefixPattern = /^(pro thinking)\s*[•:\-–—]*\s*/i;
139
+ const normalized = prefixPattern.test(trimmed)
140
+ ? trimmed.replace(prefixPattern, "").trim()
141
+ : trimmed;
142
+ if (!normalized) {
143
+ return "";
144
+ }
145
+ const normalizedKey = normalized.toLowerCase();
146
+ return SAFE_THINKING_STATUS_MESSAGES.has(normalizedKey) ? normalizedKey : "active";
147
+ }
148
+ function buildThinkingStatusExpression() {
149
+ const conversationLiteral = JSON.stringify(CONVERSATION_TURN_SELECTOR);
150
+ const assistantLiteral = JSON.stringify(ASSISTANT_ROLE_SELECTOR);
151
+ const selectors = [
152
+ "span.loading-shimmer",
153
+ "span.flex.items-center.gap-1.truncate.text-start.align-middle.text-token-text-tertiary",
154
+ '[data-testid*="thinking"]',
155
+ '[data-testid*="reasoning"]',
156
+ '[role="status"]',
157
+ '[aria-live="polite"]',
158
+ ];
159
+ const keywords = ["pro thinking", "thinking", "reasoning"];
160
+ const selectorLiteral = JSON.stringify(selectors);
161
+ const keywordsLiteral = JSON.stringify(keywords);
162
+ return `(async () => {
163
+ const CONVERSATION_SELECTOR = ${conversationLiteral};
164
+ const ASSISTANT_SELECTOR = ${assistantLiteral};
165
+ const selectors = ${selectorLiteral};
166
+ const keywords = ${keywordsLiteral};
167
+ const normalize = (value) =>
168
+ String(value || '')
169
+ .normalize('NFD')
170
+ .replace(/[\\u0300-\\u036f]/g, '')
171
+ .toLowerCase()
172
+ .replace(/\\s+/g, ' ')
173
+ .trim();
174
+ const isVisible = (node) => {
175
+ if (!(node instanceof HTMLElement)) return false;
176
+ const rect = node.getBoundingClientRect();
177
+ if (!rect || rect.width <= 0 || rect.height <= 0) return false;
178
+ const style = window.getComputedStyle(node);
179
+ if (
180
+ style.display === 'none' ||
181
+ style.visibility === 'hidden' ||
182
+ (style.opacity !== '' && Number(style.opacity) === 0)
183
+ ) {
184
+ return false;
185
+ }
186
+ return rect.bottom >= 0 && rect.right >= 0 && rect.top <= window.innerHeight && rect.left <= window.innerWidth;
187
+ };
188
+ const labelFor = (node) =>
189
+ normalize([
190
+ node.textContent,
191
+ node.getAttribute?.('aria-label'),
192
+ node.getAttribute?.('title'),
193
+ node.getAttribute?.('data-testid'),
194
+ ].filter(Boolean).join(' '));
195
+ const looksLikeThinking = (node) => {
196
+ const label = labelFor(node);
197
+ return (
198
+ label.includes('thinking') ||
199
+ label.includes('reasoning') ||
200
+ label.includes('pro thinking') ||
201
+ label.includes('myslen') ||
202
+ label.includes('mysl') ||
203
+ label.includes('rozumow')
204
+ );
205
+ };
206
+ const isComposerAdjacent = (node) =>
207
+ Boolean(node.closest?.('[contenteditable="true"], textarea, [data-testid*="composer"], [id*="composer"]'));
208
+ const isAssistantTurn = (node) => {
209
+ if (!(node instanceof HTMLElement)) return false;
210
+ const turnAttr = (node.getAttribute('data-turn') || node.dataset?.turn || '').toLowerCase();
211
+ if (turnAttr === 'assistant') return true;
212
+ const role = (node.getAttribute('data-message-author-role') || node.dataset?.messageAuthorRole || '').toLowerCase();
213
+ if (role === 'assistant') return true;
214
+ const testId = (node.getAttribute('data-testid') || '').toLowerCase();
215
+ if (testId.includes('assistant')) return true;
216
+ return Boolean(node.querySelector(ASSISTANT_SELECTOR) || node.querySelector('[data-testid*="assistant"]'));
217
+ };
218
+ const latestAssistantTurn = () => {
219
+ const turns = Array.from(document.querySelectorAll(CONVERSATION_SELECTOR));
220
+ for (let index = turns.length - 1; index >= 0; index -= 1) {
221
+ if (isAssistantTurn(turns[index])) {
222
+ return turns[index];
223
+ }
224
+ }
225
+ return null;
226
+ };
227
+ const findThinkingDisclosure = (scope) => {
228
+ const candidates = Array.from(
229
+ scope.querySelectorAll(
230
+ [
231
+ 'button',
232
+ '[role="button"]',
233
+ '[aria-expanded]',
234
+ '[data-testid*="thinking"]',
235
+ '[data-testid*="reasoning"]',
236
+ ].join(','),
237
+ ),
238
+ );
239
+ for (const node of candidates) {
240
+ if (!(node instanceof HTMLElement)) continue;
241
+ if (!isVisible(node) || isComposerAdjacent(node) || !looksLikeThinking(node)) continue;
242
+ if (node.getAttribute('aria-haspopup') === 'menu') continue;
243
+ if (node.dataset?.oracleThinkingProbed === 'true') continue;
244
+ const expanded = normalize(node.getAttribute('aria-expanded'));
245
+ if (expanded !== 'false') {
246
+ continue;
247
+ }
248
+ return node;
249
+ }
250
+ return null;
251
+ };
252
+ const findProgressPercent = (scope) => {
253
+ const progressNodes = Array.from(
254
+ scope.querySelectorAll('progress, [role="progressbar"], [aria-valuenow], [data-testid*="progress"], [class*="progress"]'),
255
+ );
256
+ const readNumeric = (raw) => {
257
+ if (raw == null || raw === '') return null;
258
+ const value = Number(String(raw).replace('%', '').trim());
259
+ return Number.isFinite(value) ? value : null;
260
+ };
261
+ const readStylePercent = (node) => {
262
+ const style = node instanceof HTMLElement ? window.getComputedStyle(node) : null;
263
+ if (!style) return null;
264
+ const widthMatch = String(node.style?.width || style.width || '').match(
265
+ /([0-9]+(?:\\.[0-9]+)?)%/,
266
+ );
267
+ if (widthMatch) return readNumeric(widthMatch[1]);
268
+ const transform = String(style.transform || '');
269
+ const scaleMatch = transform.match(/scaleX\\(([0-9.]+)\\)/);
270
+ if (scaleMatch) {
271
+ const scale = readNumeric(scaleMatch[1]);
272
+ return scale == null ? null : scale * 100;
273
+ }
274
+ const matrixMatch = transform.match(/matrix\\(([0-9.\\-]+),/);
275
+ if (matrixMatch) {
276
+ const scale = readNumeric(matrixMatch[1]);
277
+ return scale == null ? null : scale * 100;
278
+ }
279
+ return null;
280
+ };
281
+ for (const node of progressNodes) {
282
+ if (!(node instanceof HTMLElement) || !isVisible(node)) continue;
283
+ const ariaNow = readNumeric(node.getAttribute('aria-valuenow'));
284
+ if (ariaNow != null) {
285
+ const ariaMin = readNumeric(node.getAttribute('aria-valuemin')) ?? 0;
286
+ const ariaMax = readNumeric(node.getAttribute('aria-valuemax')) ?? 100;
287
+ const span = Math.max(ariaMax - ariaMin, 1);
288
+ return Math.max(0, Math.min(100, ((ariaNow - ariaMin) / span) * 100));
289
+ }
290
+ if (node instanceof HTMLProgressElement && Number.isFinite(node.value) && Number.isFinite(node.max) && node.max > 0) {
291
+ return Math.max(0, Math.min(100, (node.value / node.max) * 100));
292
+ }
293
+ const stylePercent = readStylePercent(node);
294
+ if (stylePercent != null) {
295
+ return Math.max(0, Math.min(100, stylePercent));
296
+ }
297
+ }
298
+ return null;
299
+ };
300
+ const findThinkingPanel = () => {
301
+ const candidates = Array.from(
302
+ document.querySelectorAll(
303
+ [
304
+ 'aside',
305
+ '[role="complementary"]',
306
+ '[role="dialog"]',
307
+ '[data-testid*="thinking"]',
308
+ '[data-testid*="reasoning"]',
309
+ '[data-testid*="sidebar"]',
310
+ '[class*="sidecar"]',
311
+ '[class*="sidebar"]',
312
+ ].join(','),
313
+ ),
314
+ );
315
+ for (const node of candidates) {
316
+ if (!(node instanceof HTMLElement) || !isVisible(node) || isComposerAdjacent(node)) continue;
317
+ const rect = node.getBoundingClientRect();
318
+ const rightSidePanel = rect.left >= window.innerWidth * 0.35 && rect.width >= 180 && rect.height >= 120;
319
+ const hasProgress = findProgressPercent(node) != null;
320
+ if (hasProgress || (rightSidePanel && looksLikeThinking(node))) {
321
+ return node;
322
+ }
323
+ }
324
+ return null;
325
+ };
326
+ const existingPanel = findThinkingPanel();
327
+ if (existingPanel) {
328
+ return {
329
+ message: 'thinking sidecar active',
330
+ source: 'sidecar',
331
+ progressPercent: findProgressPercent(existingPanel),
332
+ panelOpened: false,
333
+ panelVisible: true,
334
+ };
335
+ }
336
+ let panelOpened = false;
337
+ const currentTurn = latestAssistantTurn();
338
+ const disclosure = currentTurn ? findThinkingDisclosure(currentTurn) : null;
339
+ if (disclosure) {
340
+ try {
341
+ disclosure.dataset.oracleThinkingProbed = 'true';
342
+ disclosure.click();
343
+ panelOpened = true;
344
+ await new Promise((resolve) => setTimeout(resolve, 200));
345
+ } catch {
346
+ // non-fatal; fall through to passive status detection
347
+ }
348
+ }
349
+ const panel = findThinkingPanel();
350
+ if (panel) {
351
+ const progressPercent = findProgressPercent(panel);
352
+ return {
353
+ message: panelOpened ? 'thinking sidecar opened' : 'thinking sidecar active',
354
+ source: 'sidecar',
355
+ progressPercent,
356
+ panelOpened,
357
+ panelVisible: true,
358
+ };
359
+ }
360
+ const nodes = new Set();
361
+ for (const selector of selectors) {
362
+ document.querySelectorAll(selector).forEach((node) => nodes.add(node));
363
+ }
364
+ for (const node of nodes) {
365
+ if (!(node instanceof HTMLElement)) {
366
+ continue;
367
+ }
368
+ const text = node.textContent?.trim();
369
+ if (!text) {
370
+ continue;
371
+ }
372
+ const classLabel = String(node.className || '').toLowerCase();
373
+ const dataLabel = ((node.getAttribute('data-testid') || '') + ' ' + (node.getAttribute('aria-label') || ''))
374
+ .toLowerCase();
375
+ const normalizedText = text.toLowerCase();
376
+ const matches = keywords.some((keyword) =>
377
+ normalizedText.includes(keyword) || classLabel.includes(keyword) || dataLabel.includes(keyword)
378
+ );
379
+ if (matches) {
380
+ return {
381
+ message: 'active',
382
+ source: 'inline',
383
+ };
384
+ }
385
+ }
386
+ return null;
387
+ })()`;
388
+ }
389
+ export const startThinkingStatusMonitorForTest = startThinkingStatusMonitor;
390
+ export const readThinkingStatusForTest = readThinkingStatus;
391
+ export const buildThinkingStatusExpressionForTest = buildThinkingStatusExpression;