@steipete/oracle 0.7.1 → 0.7.3
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/dist/src/browser/actions/assistantResponse.js +53 -33
- package/dist/src/browser/actions/attachments.js +276 -133
- package/dist/src/browser/actions/modelSelection.js +33 -2
- package/dist/src/browser/actions/promptComposer.js +38 -45
- package/dist/src/browser/chromeLifecycle.js +2 -0
- package/dist/src/browser/config.js +7 -2
- package/dist/src/browser/index.js +12 -2
- package/dist/src/browser/pageActions.js +1 -1
- package/dist/src/browser/reattach.js +192 -17
- package/dist/src/browser/utils.js +10 -0
- package/dist/src/browserMode.js +1 -1
- package/dist/src/cli/browserConfig.js +11 -6
- package/dist/src/cli/notifier.js +8 -2
- package/dist/src/cli/oscUtils.js +1 -19
- package/dist/src/cli/sessionDisplay.js +6 -3
- package/dist/src/cli/sessionTable.js +5 -1
- package/dist/src/oracle/files.js +8 -1
- package/dist/src/oracle/modelResolver.js +11 -4
- package/dist/src/oracle/multiModelRunner.js +3 -14
- package/dist/src/oracle/oscProgress.js +12 -61
- package/dist/src/oracle/run.js +62 -34
- package/dist/src/sessionManager.js +91 -2
- 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/build-notifier.sh +0 -0
- package/package.json +43 -26
- 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 +24 -0
- package/vendor/oracle-notifier/build-notifier.sh +0 -0
|
@@ -4,6 +4,7 @@ import { logDomFailure, logConversationSnapshot, buildConversationDebugExpressio
|
|
|
4
4
|
import { buildClickDispatcher } from './domEvents.js';
|
|
5
5
|
const ASSISTANT_POLL_TIMEOUT_ERROR = 'assistant-response-watchdog-timeout';
|
|
6
6
|
export async function waitForAssistantResponse(Runtime, timeoutMs, logger) {
|
|
7
|
+
const start = Date.now();
|
|
7
8
|
logger('Waiting for ChatGPT response');
|
|
8
9
|
const expression = buildResponseObserverExpression(timeoutMs);
|
|
9
10
|
const evaluationPromise = Runtime.evaluate({ expression, awaitPromise: true, returnByValue: true });
|
|
@@ -61,7 +62,24 @@ export async function waitForAssistantResponse(Runtime, timeoutMs, logger) {
|
|
|
61
62
|
throw new Error('Unable to capture assistant response');
|
|
62
63
|
}
|
|
63
64
|
const refreshed = await refreshAssistantSnapshot(Runtime, parsed, logger);
|
|
64
|
-
|
|
65
|
+
const candidate = refreshed ?? parsed;
|
|
66
|
+
// The evaluation path can race ahead of completion. If ChatGPT is still streaming, wait for the watchdog poller.
|
|
67
|
+
const elapsedMs = Date.now() - start;
|
|
68
|
+
const remainingMs = Math.max(0, timeoutMs - elapsedMs);
|
|
69
|
+
if (remainingMs > 0) {
|
|
70
|
+
const [stopVisible, completionVisible] = await Promise.all([
|
|
71
|
+
isStopButtonVisible(Runtime),
|
|
72
|
+
isCompletionVisible(Runtime),
|
|
73
|
+
]);
|
|
74
|
+
if (stopVisible && !completionVisible) {
|
|
75
|
+
logger('Assistant still generating; waiting for completion');
|
|
76
|
+
const completed = await pollAssistantCompletion(Runtime, remainingMs);
|
|
77
|
+
if (completed) {
|
|
78
|
+
return completed;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return candidate;
|
|
65
83
|
}
|
|
66
84
|
export async function readAssistantSnapshot(Runtime) {
|
|
67
85
|
const { result } = await Runtime.evaluate({ expression: buildAssistantSnapshotExpression(), returnByValue: true });
|
|
@@ -118,11 +136,14 @@ async function parseAssistantEvaluationResult(Runtime, evaluation, timeoutMs, lo
|
|
|
118
136
|
const messageId = typeof result.value.messageId === 'string'
|
|
119
137
|
? (result.value.messageId ?? undefined)
|
|
120
138
|
: undefined;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
139
|
+
const text = cleanAssistantText(String(result.value.text ?? ''));
|
|
140
|
+
const normalized = text.toLowerCase();
|
|
141
|
+
if (normalized.includes('answer now') &&
|
|
142
|
+
(normalized.includes('pro thinking') || normalized.includes('chatgpt said'))) {
|
|
143
|
+
const recovered = await recoverAssistantResponse(Runtime, Math.min(timeoutMs, 10_000), logger);
|
|
144
|
+
return recovered ?? null;
|
|
145
|
+
}
|
|
146
|
+
return { text, html, meta: { turnId, messageId } };
|
|
126
147
|
}
|
|
127
148
|
const fallbackText = typeof result.value === 'string' ? cleanAssistantText(result.value) : '';
|
|
128
149
|
if (!fallbackText) {
|
|
@@ -261,7 +282,7 @@ function normalizeAssistantSnapshot(snapshot) {
|
|
|
261
282
|
}
|
|
262
283
|
const normalized = text.toLowerCase();
|
|
263
284
|
// "Pro thinking" often renders a placeholder turn containing an "Answer now" gate.
|
|
264
|
-
// Treat it as incomplete so browser mode keeps waiting
|
|
285
|
+
// Treat it as incomplete so browser mode keeps waiting for the real assistant text.
|
|
265
286
|
if (normalized.includes('answer now') && (normalized.includes('pro thinking') || normalized.includes('chatgpt said'))) {
|
|
266
287
|
return null;
|
|
267
288
|
}
|
|
@@ -303,7 +324,10 @@ function buildResponseObserverExpression(timeoutMs) {
|
|
|
303
324
|
const CONVERSATION_SELECTOR = ${conversationLiteral};
|
|
304
325
|
const ASSISTANT_SELECTOR = ${assistantLiteral};
|
|
305
326
|
const settleDelayMs = 800;
|
|
306
|
-
const
|
|
327
|
+
const isAnswerNowPlaceholder = (snapshot) => {
|
|
328
|
+
const normalized = String(snapshot?.text ?? '').toLowerCase();
|
|
329
|
+
return normalized.includes('answer now') && (normalized.includes('pro thinking') || normalized.includes('chatgpt said'));
|
|
330
|
+
};
|
|
307
331
|
|
|
308
332
|
// Helper to detect assistant turns - matches buildAssistantExtractor logic
|
|
309
333
|
const isAssistantTurn = (node) => {
|
|
@@ -324,7 +348,8 @@ function buildResponseObserverExpression(timeoutMs) {
|
|
|
324
348
|
const deadline = Date.now() + ${timeoutMs};
|
|
325
349
|
let stopInterval = null;
|
|
326
350
|
const observer = new MutationObserver(() => {
|
|
327
|
-
const
|
|
351
|
+
const extractedRaw = extractFromTurns();
|
|
352
|
+
const extracted = extractedRaw && !isAnswerNowPlaceholder(extractedRaw) ? extractedRaw : null;
|
|
328
353
|
if (extracted) {
|
|
329
354
|
observer.disconnect();
|
|
330
355
|
if (stopInterval) {
|
|
@@ -341,11 +366,6 @@ function buildResponseObserverExpression(timeoutMs) {
|
|
|
341
366
|
});
|
|
342
367
|
observer.observe(document.body, { childList: true, subtree: true, characterData: true });
|
|
343
368
|
stopInterval = setInterval(() => {
|
|
344
|
-
// Pro thinking can gate the response behind an "Answer now" button. Keep clicking it while present.
|
|
345
|
-
const answerNow = Array.from(document.querySelectorAll('button,span')).find((el) => (el?.textContent || '').trim().toLowerCase() === ANSWER_NOW_LABEL);
|
|
346
|
-
if (answerNow) {
|
|
347
|
-
dispatchClickSequence(answerNow.closest('button') ?? answerNow);
|
|
348
|
-
}
|
|
349
369
|
const stop = document.querySelector(STOP_SELECTOR);
|
|
350
370
|
if (!stop) {
|
|
351
371
|
return;
|
|
@@ -387,28 +407,28 @@ function buildResponseObserverExpression(timeoutMs) {
|
|
|
387
407
|
const waitForSettle = async (snapshot) => {
|
|
388
408
|
const settleWindowMs = 5000;
|
|
389
409
|
const settleIntervalMs = 400;
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const finishedVisible = isLastAssistantTurnFinished();
|
|
410
|
+
const deadline = Date.now() + settleWindowMs;
|
|
411
|
+
let latest = snapshot;
|
|
412
|
+
let lastLength = snapshot?.text?.length ?? 0;
|
|
413
|
+
while (Date.now() < deadline) {
|
|
414
|
+
await new Promise((resolve) => setTimeout(resolve, settleIntervalMs));
|
|
415
|
+
const refreshed = extractFromTurns();
|
|
416
|
+
if (refreshed && !isAnswerNowPlaceholder(refreshed) && (refreshed.text?.length ?? 0) >= lastLength) {
|
|
417
|
+
latest = refreshed;
|
|
418
|
+
lastLength = refreshed.text?.length ?? lastLength;
|
|
419
|
+
}
|
|
420
|
+
const stopVisible = Boolean(document.querySelector(STOP_SELECTOR));
|
|
421
|
+
const finishedVisible = isLastAssistantTurnFinished();
|
|
403
422
|
|
|
404
|
-
|
|
405
|
-
|
|
423
|
+
if (!stopVisible || finishedVisible) {
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
406
426
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
};
|
|
427
|
+
return latest ?? snapshot;
|
|
428
|
+
};
|
|
410
429
|
|
|
411
|
-
const
|
|
430
|
+
const extractedRaw = extractFromTurns();
|
|
431
|
+
const extracted = extractedRaw && !isAnswerNowPlaceholder(extractedRaw) ? extractedRaw : null;
|
|
412
432
|
if (extracted) {
|
|
413
433
|
return waitForSettle(extracted);
|
|
414
434
|
}
|