@steipete/oracle 0.12.0 → 0.13.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/README.md +54 -54
- package/dist/bin/oracle-cli.js +15 -6
- package/dist/bin/oracle-mcp.js +0 -0
- package/dist/src/browser/actions/modelSelection.js +126 -25
- package/dist/src/browser/actions/navigation.js +89 -27
- package/dist/src/browser/actions/promptComposer.js +196 -46
- package/dist/src/browser/actions/thinkingTime.js +111 -12
- package/dist/src/browser/config.js +2 -0
- package/dist/src/browser/index.js +43 -4
- package/dist/src/browser/providers/chatgptDomProvider.js +2 -0
- package/dist/src/browser/reattachability.js +22 -0
- package/dist/src/browser/sessionRunner.js +1 -0
- package/dist/src/cli/bridge/doctor.js +7 -2
- package/dist/src/cli/browserConfig.js +9 -1
- package/dist/src/cli/browserDefaults.js +3 -0
- package/dist/src/cli/engine.js +6 -2
- package/dist/src/cli/options.js +4 -0
- package/dist/src/cli/runOptions.js +9 -20
- package/dist/src/cli/sessionDisplay.js +8 -0
- package/dist/src/cli/sessionRunner.js +49 -5
- package/dist/src/config.js +164 -9
- package/dist/src/oracle/providerRoutePlan.js +29 -2
- package/dist/src/oracle/run.js +50 -156
- package/dist/src/sessionManager.js +38 -22
- package/package.json +14 -13
|
@@ -11,9 +11,11 @@ import { buildClickDispatcher } from "./domEvents.js";
|
|
|
11
11
|
*
|
|
12
12
|
* @param level - The thinking time intensity: 'light', 'standard', 'extended', or 'heavy'
|
|
13
13
|
*/
|
|
14
|
-
export async function ensureThinkingTime(Runtime, level, logger) {
|
|
15
|
-
const result = await evaluateThinkingTimeSelection(Runtime, level);
|
|
14
|
+
export async function ensureThinkingTime(Runtime, level, logger, desiredModel) {
|
|
15
|
+
const result = await evaluateThinkingTimeSelection(Runtime, level, desiredModel);
|
|
16
16
|
const capitalizedLevel = level.charAt(0).toUpperCase() + level.slice(1);
|
|
17
|
+
const targetModelKind = inferThinkingTargetModelKind(desiredModel);
|
|
18
|
+
const strictProEffort = targetModelKind === "pro" && level === "extended";
|
|
17
19
|
switch (result?.status) {
|
|
18
20
|
case "already-selected":
|
|
19
21
|
logger(`Thinking time: ${result.label ?? capitalizedLevel} (already selected)`);
|
|
@@ -23,13 +25,26 @@ export async function ensureThinkingTime(Runtime, level, logger) {
|
|
|
23
25
|
return;
|
|
24
26
|
case "chip-not-found":
|
|
25
27
|
case "menu-not-found":
|
|
26
|
-
case "option-not-found":
|
|
28
|
+
case "option-not-found":
|
|
29
|
+
case "model-kind-not-found": {
|
|
27
30
|
await logDomFailure(Runtime, logger, `thinking-${result.status}`);
|
|
28
|
-
|
|
31
|
+
const kindHint = result.status === "model-kind-not-found" && result.modelKind
|
|
32
|
+
? ` for ${result.modelKind}`
|
|
33
|
+
: targetModelKind
|
|
34
|
+
? ` for ${targetModelKind}`
|
|
35
|
+
: "";
|
|
36
|
+
const message = `Thinking time: ${result.status.replaceAll("-", " ")}${kindHint} (requested ${capitalizedLevel})`;
|
|
37
|
+
if (strictProEffort) {
|
|
38
|
+
throw new Error(`${message}; refusing to submit without confirmed Pro Extended.`);
|
|
39
|
+
}
|
|
40
|
+
logger(`${message}; continuing with ChatGPT default.`);
|
|
29
41
|
return;
|
|
30
42
|
}
|
|
31
43
|
default: {
|
|
32
44
|
await logDomFailure(Runtime, logger, "thinking-time-unknown");
|
|
45
|
+
if (strictProEffort) {
|
|
46
|
+
throw new Error(`Thinking time: unknown outcome selecting ${capitalizedLevel}; refusing to submit without confirmed Pro Extended.`);
|
|
47
|
+
}
|
|
33
48
|
logger(`Thinking time: unknown outcome selecting ${capitalizedLevel}; continuing with ChatGPT default.`);
|
|
34
49
|
return;
|
|
35
50
|
}
|
|
@@ -40,9 +55,9 @@ export async function ensureThinkingTime(Runtime, level, logger) {
|
|
|
40
55
|
* Safe by default: if the pill/menu/option isn't present, we continue without throwing.
|
|
41
56
|
* @param level - The thinking time intensity: 'light', 'standard', 'extended', or 'heavy'
|
|
42
57
|
*/
|
|
43
|
-
export async function ensureThinkingTimeIfAvailable(Runtime, level, logger) {
|
|
58
|
+
export async function ensureThinkingTimeIfAvailable(Runtime, level, logger, desiredModel) {
|
|
44
59
|
try {
|
|
45
|
-
const result = await evaluateThinkingTimeSelection(Runtime, level);
|
|
60
|
+
const result = await evaluateThinkingTimeSelection(Runtime, level, desiredModel);
|
|
46
61
|
const capitalizedLevel = level.charAt(0).toUpperCase() + level.slice(1);
|
|
47
62
|
switch (result?.status) {
|
|
48
63
|
case "already-selected":
|
|
@@ -54,6 +69,7 @@ export async function ensureThinkingTimeIfAvailable(Runtime, level, logger) {
|
|
|
54
69
|
case "chip-not-found":
|
|
55
70
|
case "menu-not-found":
|
|
56
71
|
case "option-not-found":
|
|
72
|
+
case "model-kind-not-found":
|
|
57
73
|
if (logger.verbose) {
|
|
58
74
|
logger(`Thinking time: ${result.status.replaceAll("-", " ")}; continuing with default.`);
|
|
59
75
|
}
|
|
@@ -74,19 +90,20 @@ export async function ensureThinkingTimeIfAvailable(Runtime, level, logger) {
|
|
|
74
90
|
return false;
|
|
75
91
|
}
|
|
76
92
|
}
|
|
77
|
-
async function evaluateThinkingTimeSelection(Runtime, level) {
|
|
93
|
+
async function evaluateThinkingTimeSelection(Runtime, level, desiredModel) {
|
|
78
94
|
const outcome = await Runtime.evaluate({
|
|
79
|
-
expression: buildThinkingTimeExpression(level),
|
|
95
|
+
expression: buildThinkingTimeExpression(level, desiredModel),
|
|
80
96
|
awaitPromise: true,
|
|
81
97
|
returnByValue: true,
|
|
82
98
|
});
|
|
83
99
|
return outcome.result?.value;
|
|
84
100
|
}
|
|
85
|
-
function buildThinkingTimeExpression(level) {
|
|
101
|
+
function buildThinkingTimeExpression(level, desiredModel) {
|
|
86
102
|
const menuContainerLiteral = JSON.stringify(MENU_CONTAINER_SELECTOR);
|
|
87
103
|
const menuItemLiteral = JSON.stringify(MENU_ITEM_SELECTOR);
|
|
88
104
|
const modelButtonLiteral = JSON.stringify(MODEL_BUTTON_SELECTOR);
|
|
89
105
|
const targetLevelLiteral = JSON.stringify(level.toLowerCase());
|
|
106
|
+
const targetModelKindLiteral = JSON.stringify(inferThinkingTargetModelKind(desiredModel));
|
|
90
107
|
return `(async () => {
|
|
91
108
|
${buildClickDispatcher()}
|
|
92
109
|
|
|
@@ -94,6 +111,7 @@ function buildThinkingTimeExpression(level) {
|
|
|
94
111
|
const MENU_ITEM_SELECTOR = ${menuItemLiteral};
|
|
95
112
|
const MODEL_BUTTON_SELECTOR = ${modelButtonLiteral};
|
|
96
113
|
const TARGET_LEVEL = ${targetLevelLiteral};
|
|
114
|
+
const TARGET_MODEL_KIND = ${targetModelKindLiteral};
|
|
97
115
|
|
|
98
116
|
// Bilingual matchers: English level token + observed Chinese variants.
|
|
99
117
|
const LEVEL_TOKENS = {
|
|
@@ -119,6 +137,7 @@ function buildThinkingTimeExpression(level) {
|
|
|
119
137
|
const t = normalize(text);
|
|
120
138
|
return targetTokens.some((tok) => t.includes(String(tok).toLowerCase()));
|
|
121
139
|
};
|
|
140
|
+
const hasToken = (text, token) => normalize(text).split(' ').includes(token);
|
|
122
141
|
const optionIsSelected = (node) => {
|
|
123
142
|
if (!(node instanceof HTMLElement)) return false;
|
|
124
143
|
const ariaChecked = node.getAttribute('aria-checked');
|
|
@@ -207,6 +226,7 @@ function buildThinkingTimeExpression(level) {
|
|
|
207
226
|
|
|
208
227
|
const findModelButton = () => document.querySelector(MODEL_BUTTON_SELECTOR);
|
|
209
228
|
const findTrailingButtons = () => Array.from(document.querySelectorAll(TRAILING_SELECTOR));
|
|
229
|
+
const KIND_NOT_FOUND = { kindNotFound: true };
|
|
210
230
|
const findEffortRow = (node) => {
|
|
211
231
|
let current = node instanceof HTMLElement ? node.parentElement : null;
|
|
212
232
|
while (current && current !== document.body) {
|
|
@@ -227,6 +247,58 @@ function buildThinkingTimeExpression(level) {
|
|
|
227
247
|
),
|
|
228
248
|
);
|
|
229
249
|
};
|
|
250
|
+
const rowForTrailing = (trailing) =>
|
|
251
|
+
trailing.closest('[role="menuitem"], [role="menuitemradio"], [data-radix-collection-item]');
|
|
252
|
+
const rowTextForTrailing = (trailing) => {
|
|
253
|
+
const row = rowForTrailing(trailing) || findEffortRow(trailing);
|
|
254
|
+
return normalize(
|
|
255
|
+
(row?.getAttribute?.('aria-label') ?? '') + ' ' +
|
|
256
|
+
(row?.getAttribute?.('data-testid') ?? '') + ' ' +
|
|
257
|
+
(row?.textContent ?? '') + ' ' +
|
|
258
|
+
(trailing.getAttribute?.('aria-label') ?? '') + ' ' +
|
|
259
|
+
(trailing.getAttribute?.('data-testid') ?? '')
|
|
260
|
+
);
|
|
261
|
+
};
|
|
262
|
+
const testIdTextForTrailing = (trailing) => {
|
|
263
|
+
const row = rowForTrailing(trailing) || findEffortRow(trailing);
|
|
264
|
+
return normalize(
|
|
265
|
+
(row?.getAttribute?.('data-testid') ?? '') + ' ' +
|
|
266
|
+
(trailing.getAttribute?.('data-testid') ?? '')
|
|
267
|
+
);
|
|
268
|
+
};
|
|
269
|
+
const modelKindFromTrailing = (trailing) => {
|
|
270
|
+
const idText = testIdTextForTrailing(trailing);
|
|
271
|
+
if (!idText.includes('model switcher')) return null;
|
|
272
|
+
const modelPart = normalize(idText.replace(/\\bthinking effort\\b.*$/, ''));
|
|
273
|
+
if (hasToken(modelPart, 'pro')) return 'pro';
|
|
274
|
+
if (hasToken(modelPart, 'thinking')) return 'thinking';
|
|
275
|
+
if (hasToken(modelPart, 'instant')) return 'instant';
|
|
276
|
+
return null;
|
|
277
|
+
};
|
|
278
|
+
const trailingMatchesTargetModelKind = (trailing) => {
|
|
279
|
+
if (!TARGET_MODEL_KIND) return false;
|
|
280
|
+
const idKind = modelKindFromTrailing(trailing);
|
|
281
|
+
if (idKind) return idKind === TARGET_MODEL_KIND;
|
|
282
|
+
const text = rowTextForTrailing(trailing);
|
|
283
|
+
if (TARGET_MODEL_KIND === 'pro') {
|
|
284
|
+
return hasToken(text, 'pro') && !hasToken(text, 'thinking');
|
|
285
|
+
}
|
|
286
|
+
if (TARGET_MODEL_KIND === 'thinking') {
|
|
287
|
+
return hasToken(text, 'thinking') && !hasToken(text, 'pro');
|
|
288
|
+
}
|
|
289
|
+
if (TARGET_MODEL_KIND === 'instant') {
|
|
290
|
+
return hasToken(text, 'instant') && !hasToken(text, 'thinking') && !hasToken(text, 'pro');
|
|
291
|
+
}
|
|
292
|
+
return false;
|
|
293
|
+
};
|
|
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
|
+
const pickSingleStableTrailing = (trailings) => {
|
|
299
|
+
const visible = trailings.filter((t) => hasStableBox(t));
|
|
300
|
+
return visible.length === 1 ? visible[0] : null;
|
|
301
|
+
};
|
|
230
302
|
const pickTrailingForCurrentModel = () => {
|
|
231
303
|
const trailings = findTrailingButtons();
|
|
232
304
|
if (trailings.length === 0) return null;
|
|
@@ -236,6 +308,10 @@ function buildThinkingTimeExpression(level) {
|
|
|
236
308
|
const row = findEffortRow(t);
|
|
237
309
|
if (rowIsSelected(row)) return t;
|
|
238
310
|
}
|
|
311
|
+
if (TARGET_MODEL_KIND) {
|
|
312
|
+
const targetTrailings = trailings.filter((t) => trailingMatchesTargetModelKind(t));
|
|
313
|
+
return pickSingleStableTrailing(targetTrailings) || KIND_NOT_FOUND;
|
|
314
|
+
}
|
|
239
315
|
return null;
|
|
240
316
|
};
|
|
241
317
|
|
|
@@ -243,7 +319,6 @@ function buildThinkingTimeExpression(level) {
|
|
|
243
319
|
if (!modelBtn) {
|
|
244
320
|
return { status: 'chip-not-found' };
|
|
245
321
|
}
|
|
246
|
-
|
|
247
322
|
// Open model menu (idempotent — leaves it open if already open).
|
|
248
323
|
if (modelBtn.getAttribute('aria-expanded') !== 'true') {
|
|
249
324
|
dispatchClickSequence(modelBtn);
|
|
@@ -261,6 +336,10 @@ function buildThinkingTimeExpression(level) {
|
|
|
261
336
|
closeOpenMenus();
|
|
262
337
|
return { status: 'chip-not-found' };
|
|
263
338
|
}
|
|
339
|
+
if (trailing.kindNotFound) {
|
|
340
|
+
closeOpenMenus();
|
|
341
|
+
return { status: 'model-kind-not-found', modelKind: TARGET_MODEL_KIND };
|
|
342
|
+
}
|
|
264
343
|
|
|
265
344
|
dispatchClickSequence(trailing);
|
|
266
345
|
await sleep(STEP_WAIT_MS);
|
|
@@ -313,6 +392,26 @@ function buildThinkingTimeExpression(level) {
|
|
|
313
392
|
return { status: already ? 'already-selected' : 'switched', label };
|
|
314
393
|
})()`;
|
|
315
394
|
}
|
|
316
|
-
export function buildThinkingTimeExpressionForTest(level = "extended") {
|
|
317
|
-
return buildThinkingTimeExpression(level);
|
|
395
|
+
export function buildThinkingTimeExpressionForTest(level = "extended", desiredModel) {
|
|
396
|
+
return buildThinkingTimeExpression(level, desiredModel);
|
|
397
|
+
}
|
|
398
|
+
function inferThinkingTargetModelKind(desiredModel) {
|
|
399
|
+
const normalized = (desiredModel ?? "")
|
|
400
|
+
.toLowerCase()
|
|
401
|
+
.replace(/[^a-z0-9]+/g, " ")
|
|
402
|
+
.replace(/\s+/g, " ")
|
|
403
|
+
.trim();
|
|
404
|
+
if (!normalized)
|
|
405
|
+
return null;
|
|
406
|
+
const tokens = normalized.split(" ");
|
|
407
|
+
if (tokens.includes("pro"))
|
|
408
|
+
return "pro";
|
|
409
|
+
if (tokens.includes("thinking"))
|
|
410
|
+
return "thinking";
|
|
411
|
+
if (tokens.includes("instant"))
|
|
412
|
+
return "instant";
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
export function inferThinkingTargetModelKindForTest(desiredModel) {
|
|
416
|
+
return inferThinkingTargetModelKind(desiredModel);
|
|
318
417
|
}
|
|
@@ -26,6 +26,7 @@ export const DEFAULT_BROWSER_CONFIG = {
|
|
|
26
26
|
timeoutMs: 1_200_000,
|
|
27
27
|
debugPort: null,
|
|
28
28
|
inputTimeoutMs: 60_000,
|
|
29
|
+
attachmentTimeoutMs: 45_000,
|
|
29
30
|
assistantRecheckDelayMs: 0,
|
|
30
31
|
assistantRecheckTimeoutMs: 120_000,
|
|
31
32
|
reuseChromeWaitMs: 10_000,
|
|
@@ -80,6 +81,7 @@ export function resolveBrowserConfig(config) {
|
|
|
80
81
|
timeoutMs: config?.timeoutMs ?? defaultTimeoutMs,
|
|
81
82
|
debugPort: config?.debugPort ?? debugPortEnv ?? DEFAULT_BROWSER_CONFIG.debugPort,
|
|
82
83
|
inputTimeoutMs: config?.inputTimeoutMs ?? DEFAULT_BROWSER_CONFIG.inputTimeoutMs,
|
|
84
|
+
attachmentTimeoutMs: config?.attachmentTimeoutMs ?? DEFAULT_BROWSER_CONFIG.attachmentTimeoutMs,
|
|
83
85
|
assistantRecheckDelayMs: config?.assistantRecheckDelayMs ?? DEFAULT_BROWSER_CONFIG.assistantRecheckDelayMs,
|
|
84
86
|
assistantRecheckTimeoutMs: config?.assistantRecheckTimeoutMs ?? DEFAULT_BROWSER_CONFIG.assistantRecheckTimeoutMs,
|
|
85
87
|
reuseChromeWaitMs: config?.reuseChromeWaitMs ?? DEFAULT_BROWSER_CONFIG.reuseChromeWaitMs,
|
|
@@ -327,6 +327,7 @@ export async function runBrowserMode(options) {
|
|
|
327
327
|
const runtimeHintCb = options.runtimeHintCb;
|
|
328
328
|
let lastTargetId;
|
|
329
329
|
let lastUrl;
|
|
330
|
+
let promptSubmitted = false;
|
|
330
331
|
let tabLease = null;
|
|
331
332
|
const emitRuntimeHint = async () => {
|
|
332
333
|
if (!chrome?.port) {
|
|
@@ -340,6 +341,7 @@ export async function runBrowserMode(options) {
|
|
|
340
341
|
chromeTargetId: lastTargetId,
|
|
341
342
|
tabUrl: lastUrl,
|
|
342
343
|
conversationId,
|
|
344
|
+
promptSubmitted,
|
|
343
345
|
userDataDir,
|
|
344
346
|
controllerPid: process.pid,
|
|
345
347
|
};
|
|
@@ -357,6 +359,13 @@ export async function runBrowserMode(options) {
|
|
|
357
359
|
logger(`Failed to persist runtime hint: ${message}`);
|
|
358
360
|
}
|
|
359
361
|
};
|
|
362
|
+
const markPromptSubmitted = async () => {
|
|
363
|
+
if (promptSubmitted) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
promptSubmitted = true;
|
|
367
|
+
await emitRuntimeHint();
|
|
368
|
+
};
|
|
360
369
|
if (config.debug || process.env.CHATGPT_DEVTOOLS_TRACE === "1") {
|
|
361
370
|
logger(`[browser-mode] config: ${JSON.stringify({
|
|
362
371
|
...redactBrowserConfigForDebugLog(config),
|
|
@@ -725,7 +734,8 @@ export async function runBrowserMode(options) {
|
|
|
725
734
|
// Handle thinking time selection if specified. Deep Research owns its own effort flow.
|
|
726
735
|
const thinkingTime = config.thinkingTime;
|
|
727
736
|
if (thinkingTime && !deepResearch) {
|
|
728
|
-
|
|
737
|
+
const thinkingTargetModel = modelStrategy === "select" ? config.desiredModel : null;
|
|
738
|
+
await raceWithDisconnect(withRetries(() => ensureThinkingTime(Runtime, thinkingTime, logger, thinkingTargetModel), {
|
|
729
739
|
retries: 2,
|
|
730
740
|
delayMs: 300,
|
|
731
741
|
onRetry: (attempt, error) => {
|
|
@@ -790,7 +800,8 @@ export async function runBrowserMode(options) {
|
|
|
790
800
|
const baseTimeout = config.inputTimeoutMs ?? 30_000;
|
|
791
801
|
const perFileTimeout = 20_000;
|
|
792
802
|
const waitBudget = Math.max(baseTimeout, 45_000) + (submissionAttachments.length - 1) * perFileTimeout;
|
|
793
|
-
|
|
803
|
+
const attachmentWaitBudget = Math.max(config.attachmentTimeoutMs ?? 0, waitBudget);
|
|
804
|
+
await waitForAttachmentCompletion(Runtime, attachmentWaitBudget, attachmentNames, logger);
|
|
794
805
|
logger("All attachments uploaded");
|
|
795
806
|
}
|
|
796
807
|
let baselineTurns = await readConversationTurnCount(Runtime, logger);
|
|
@@ -801,8 +812,10 @@ export async function runBrowserMode(options) {
|
|
|
801
812
|
logger,
|
|
802
813
|
timeoutMs: config.timeoutMs,
|
|
803
814
|
inputTimeoutMs: config.inputTimeoutMs ?? undefined,
|
|
815
|
+
attachmentTimeoutMs: config.attachmentTimeoutMs ?? undefined,
|
|
804
816
|
baselineTurns: baselineTurns ?? undefined,
|
|
805
817
|
attachmentNames,
|
|
818
|
+
onPromptSubmitted: markPromptSubmitted,
|
|
806
819
|
};
|
|
807
820
|
await runProviderSubmissionFlow(chatgptDomProvider, {
|
|
808
821
|
prompt,
|
|
@@ -811,6 +824,7 @@ export async function runBrowserMode(options) {
|
|
|
811
824
|
log: logger,
|
|
812
825
|
state: providerState,
|
|
813
826
|
});
|
|
827
|
+
await markPromptSubmitted();
|
|
814
828
|
const providerBaselineTurns = providerState.baselineTurns;
|
|
815
829
|
if (typeof providerBaselineTurns === "number" && Number.isFinite(providerBaselineTurns)) {
|
|
816
830
|
baselineTurns = providerBaselineTurns;
|
|
@@ -912,6 +926,7 @@ export async function runBrowserMode(options) {
|
|
|
912
926
|
chromeTargetId: lastTargetId,
|
|
913
927
|
tabUrl: lastUrl,
|
|
914
928
|
conversationId: lastUrl ? extractConversationIdFromUrl(lastUrl) : undefined,
|
|
929
|
+
promptSubmitted,
|
|
915
930
|
controllerPid: process.pid,
|
|
916
931
|
};
|
|
917
932
|
}
|
|
@@ -996,6 +1011,7 @@ export async function runBrowserMode(options) {
|
|
|
996
1011
|
chromeTargetId: lastTargetId,
|
|
997
1012
|
tabUrl: lastUrl,
|
|
998
1013
|
conversationId: lastUrl ? extractConversationIdFromUrl(lastUrl) : undefined,
|
|
1014
|
+
promptSubmitted,
|
|
999
1015
|
controllerPid: process.pid,
|
|
1000
1016
|
},
|
|
1001
1017
|
});
|
|
@@ -1051,6 +1067,7 @@ export async function runBrowserMode(options) {
|
|
|
1051
1067
|
chromeTargetId: lastTargetId,
|
|
1052
1068
|
tabUrl: lastUrl,
|
|
1053
1069
|
conversationId: lastUrl ? extractConversationIdFromUrl(lastUrl) : undefined,
|
|
1070
|
+
promptSubmitted,
|
|
1054
1071
|
controllerPid: process.pid,
|
|
1055
1072
|
};
|
|
1056
1073
|
throw new BrowserAutomationError("Assistant response timed out before completion; reattach later to capture the answer.", { stage: "assistant-timeout", runtime, diagnostics }, error);
|
|
@@ -1298,6 +1315,7 @@ export async function runBrowserMode(options) {
|
|
|
1298
1315
|
chromeTargetId: lastTargetId,
|
|
1299
1316
|
tabUrl: lastUrl,
|
|
1300
1317
|
conversationId: lastUrl ? extractConversationIdFromUrl(lastUrl) : undefined,
|
|
1318
|
+
promptSubmitted,
|
|
1301
1319
|
controllerPid: process.pid,
|
|
1302
1320
|
};
|
|
1303
1321
|
}
|
|
@@ -1315,6 +1333,7 @@ export async function runBrowserMode(options) {
|
|
|
1315
1333
|
userDataDir,
|
|
1316
1334
|
chromeTargetId: lastTargetId,
|
|
1317
1335
|
tabUrl: lastUrl,
|
|
1336
|
+
promptSubmitted,
|
|
1318
1337
|
controllerPid: process.pid,
|
|
1319
1338
|
};
|
|
1320
1339
|
const reuseProfileHint = `oracle --engine browser --browser-manual-login ` +
|
|
@@ -1355,6 +1374,7 @@ export async function runBrowserMode(options) {
|
|
|
1355
1374
|
userDataDir,
|
|
1356
1375
|
chromeTargetId: lastTargetId,
|
|
1357
1376
|
tabUrl: lastUrl,
|
|
1377
|
+
promptSubmitted,
|
|
1358
1378
|
controllerPid: process.pid,
|
|
1359
1379
|
},
|
|
1360
1380
|
}, normalizedError);
|
|
@@ -1703,6 +1723,7 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1703
1723
|
let remoteTargetId = null;
|
|
1704
1724
|
let tabLease = null;
|
|
1705
1725
|
let lastUrl;
|
|
1726
|
+
let promptSubmitted = false;
|
|
1706
1727
|
let attachedExistingTab = false;
|
|
1707
1728
|
let ownsTarget = true;
|
|
1708
1729
|
const runtimeHintCb = options.runtimeHintCb;
|
|
@@ -1718,6 +1739,7 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1718
1739
|
chromeTargetId: remoteTargetId ?? undefined,
|
|
1719
1740
|
tabUrl: lastUrl,
|
|
1720
1741
|
conversationId: lastUrl ? extractConversationIdFromUrl(lastUrl) : undefined,
|
|
1742
|
+
promptSubmitted,
|
|
1721
1743
|
controllerPid: process.pid,
|
|
1722
1744
|
});
|
|
1723
1745
|
await tabLease?.update({
|
|
@@ -1732,6 +1754,13 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1732
1754
|
logger(`Failed to persist runtime hint: ${message}`);
|
|
1733
1755
|
}
|
|
1734
1756
|
};
|
|
1757
|
+
const markPromptSubmitted = async () => {
|
|
1758
|
+
if (promptSubmitted) {
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
promptSubmitted = true;
|
|
1762
|
+
await emitRuntimeHint();
|
|
1763
|
+
};
|
|
1735
1764
|
const startedAt = Date.now();
|
|
1736
1765
|
let answerText = "";
|
|
1737
1766
|
let answerMarkdown = "";
|
|
@@ -1848,7 +1877,8 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1848
1877
|
// Handle thinking time selection if specified. Deep Research owns its own effort flow.
|
|
1849
1878
|
const thinkingTime = config.thinkingTime;
|
|
1850
1879
|
if (thinkingTime && !deepResearch) {
|
|
1851
|
-
|
|
1880
|
+
const thinkingTargetModel = modelStrategy === "select" ? config.desiredModel : null;
|
|
1881
|
+
await withRetries(() => ensureThinkingTime(Runtime, thinkingTime, logger, thinkingTargetModel), {
|
|
1852
1882
|
retries: 2,
|
|
1853
1883
|
delayMs: 300,
|
|
1854
1884
|
onRetry: (attempt, error) => {
|
|
@@ -1892,7 +1922,8 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1892
1922
|
const baseTimeout = config.inputTimeoutMs ?? 30_000;
|
|
1893
1923
|
const perFileTimeout = 15_000;
|
|
1894
1924
|
const waitBudget = Math.max(baseTimeout, 30_000) + (submissionAttachments.length - 1) * perFileTimeout;
|
|
1895
|
-
|
|
1925
|
+
const attachmentWaitBudget = Math.max(config.attachmentTimeoutMs ?? 0, waitBudget);
|
|
1926
|
+
await waitForAttachmentCompletion(Runtime, attachmentWaitBudget, attachmentNames, logger);
|
|
1896
1927
|
logger("All attachments uploaded");
|
|
1897
1928
|
}
|
|
1898
1929
|
let baselineTurns = await readConversationTurnCount(Runtime, logger);
|
|
@@ -1902,8 +1933,10 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1902
1933
|
logger,
|
|
1903
1934
|
timeoutMs: config.timeoutMs,
|
|
1904
1935
|
inputTimeoutMs: config.inputTimeoutMs ?? undefined,
|
|
1936
|
+
attachmentTimeoutMs: config.attachmentTimeoutMs ?? undefined,
|
|
1905
1937
|
baselineTurns: baselineTurns ?? undefined,
|
|
1906
1938
|
attachmentNames,
|
|
1939
|
+
onPromptSubmitted: markPromptSubmitted,
|
|
1907
1940
|
};
|
|
1908
1941
|
await runProviderSubmissionFlow(chatgptDomProvider, {
|
|
1909
1942
|
prompt,
|
|
@@ -1912,6 +1945,7 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1912
1945
|
log: logger,
|
|
1913
1946
|
state: providerState,
|
|
1914
1947
|
});
|
|
1948
|
+
await markPromptSubmitted();
|
|
1915
1949
|
const providerBaselineTurns = providerState.baselineTurns;
|
|
1916
1950
|
if (typeof providerBaselineTurns === "number" && Number.isFinite(providerBaselineTurns)) {
|
|
1917
1951
|
baselineTurns = providerBaselineTurns;
|
|
@@ -1985,6 +2019,7 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
1985
2019
|
chromeTargetId: remoteTargetId ?? undefined,
|
|
1986
2020
|
tabUrl: lastUrl,
|
|
1987
2021
|
conversationId: lastUrl ? extractConversationIdFromUrl(lastUrl) : undefined,
|
|
2022
|
+
promptSubmitted,
|
|
1988
2023
|
controllerPid: process.pid,
|
|
1989
2024
|
};
|
|
1990
2025
|
}
|
|
@@ -2068,6 +2103,7 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2068
2103
|
chromeTargetId: remoteTargetId ?? undefined,
|
|
2069
2104
|
tabUrl: lastUrl,
|
|
2070
2105
|
conversationId: lastUrl ? extractConversationIdFromUrl(lastUrl) : undefined,
|
|
2106
|
+
promptSubmitted,
|
|
2071
2107
|
controllerPid: process.pid,
|
|
2072
2108
|
},
|
|
2073
2109
|
});
|
|
@@ -2136,6 +2172,7 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2136
2172
|
chromeTargetId: remoteTargetId ?? undefined,
|
|
2137
2173
|
tabUrl: lastUrl,
|
|
2138
2174
|
conversationId: lastUrl ? extractConversationIdFromUrl(lastUrl) : undefined,
|
|
2175
|
+
promptSubmitted,
|
|
2139
2176
|
controllerPid: process.pid,
|
|
2140
2177
|
};
|
|
2141
2178
|
throw new BrowserAutomationError("Assistant response timed out before completion; reattach later to capture the answer.", { stage: "assistant-timeout", runtime, diagnostics }, error);
|
|
@@ -2338,6 +2375,7 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2338
2375
|
chromeTargetId: remoteTargetId ?? undefined,
|
|
2339
2376
|
tabUrl: lastUrl,
|
|
2340
2377
|
conversationId: lastUrl ? extractConversationIdFromUrl(lastUrl) : undefined,
|
|
2378
|
+
promptSubmitted,
|
|
2341
2379
|
artifacts: savedArtifacts,
|
|
2342
2380
|
archive,
|
|
2343
2381
|
modelSelection: modelSelectionEvidence,
|
|
@@ -2364,6 +2402,7 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
2364
2402
|
chromeProfileRoot,
|
|
2365
2403
|
chromeTargetId: remoteTargetId ?? undefined,
|
|
2366
2404
|
tabUrl: lastUrl,
|
|
2405
|
+
promptSubmitted,
|
|
2367
2406
|
controllerPid: process.pid,
|
|
2368
2407
|
},
|
|
2369
2408
|
});
|
|
@@ -23,6 +23,8 @@ async function submitPromptViaAdapter(ctx) {
|
|
|
23
23
|
attachmentNames: state.attachmentNames ?? [],
|
|
24
24
|
baselineTurns: state.baselineTurns ?? undefined,
|
|
25
25
|
inputTimeoutMs: state.inputTimeoutMs ?? undefined,
|
|
26
|
+
attachmentTimeoutMs: state.attachmentTimeoutMs ?? undefined,
|
|
27
|
+
onPromptSubmitted: state.onPromptSubmitted,
|
|
26
28
|
}, ctx.prompt, state.logger);
|
|
27
29
|
state.committedTurns =
|
|
28
30
|
typeof committedTurns === "number" && Number.isFinite(committedTurns) ? committedTurns : null;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function hasRecoverableChatGptConversation(runtime) {
|
|
2
|
+
if (!runtime) {
|
|
3
|
+
return false;
|
|
4
|
+
}
|
|
5
|
+
if (runtime.conversationId?.trim()) {
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
const tabUrl = runtime.tabUrl?.trim();
|
|
9
|
+
if (!tabUrl) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
const url = new URL(tabUrl);
|
|
14
|
+
if (url.hostname !== "chatgpt.com" && url.hostname !== "chat.openai.com") {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
return /(?:^|\/)c\/[^/]+/.test(url.pathname);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -214,6 +214,7 @@ export async function runBrowserSessionExecution({ runOptions, browserConfig, cw
|
|
|
214
214
|
chromeTargetId: browserResult.chromeTargetId,
|
|
215
215
|
tabUrl: browserResult.tabUrl,
|
|
216
216
|
conversationId: browserResult.conversationId,
|
|
217
|
+
promptSubmitted: browserResult.promptSubmitted,
|
|
217
218
|
controllerPid: browserResult.controllerPid ?? process.pid,
|
|
218
219
|
},
|
|
219
220
|
archive: browserResult.archive,
|
|
@@ -7,8 +7,9 @@ import { checkTcpConnection, checkRemoteHealth } from "../../remote/health.js";
|
|
|
7
7
|
import { detectChromeBinary, detectChromeCookieDb } from "../../browser/detect.js";
|
|
8
8
|
import { formatCodexMcpSnippet } from "./codexConfig.js";
|
|
9
9
|
export async function runBridgeDoctor(_options) {
|
|
10
|
-
const { config: userConfig, path: configPath, loaded } = await loadUserConfig();
|
|
10
|
+
const { config: userConfig, path: configPath, paths: configPaths, loaded: userConfigLoaded, } = await loadUserConfig();
|
|
11
11
|
const version = getCliVersion();
|
|
12
|
+
const projectConfigPaths = configPaths.filter((entry) => entry !== configPath);
|
|
12
13
|
const resolvedRemote = resolveRemoteServiceConfig({
|
|
13
14
|
cliHost: undefined,
|
|
14
15
|
cliToken: undefined,
|
|
@@ -22,7 +23,11 @@ export async function runBridgeDoctor(_options) {
|
|
|
22
23
|
lines.push(chalk.dim(`OS: ${process.platform} ${os.release()} (${process.arch})`));
|
|
23
24
|
lines.push(chalk.dim(`Node: ${process.version}`));
|
|
24
25
|
lines.push(chalk.dim(`Oracle: ${version}`));
|
|
25
|
-
lines.push(chalk.dim(`Config: ${
|
|
26
|
+
lines.push(chalk.dim(`Config: ${userConfigLoaded ? configPath : `${configPath} (missing)`}`));
|
|
27
|
+
if (projectConfigPaths.length > 0) {
|
|
28
|
+
const label = projectConfigPaths.length === 1 ? "Project config" : "Project configs";
|
|
29
|
+
lines.push(chalk.dim(`${label}: ${projectConfigPaths.join(", ")}`));
|
|
30
|
+
}
|
|
26
31
|
if (userConfig.engine) {
|
|
27
32
|
lines.push(chalk.dim(`Default engine: ${userConfig.engine}`));
|
|
28
33
|
}
|
|
@@ -7,6 +7,7 @@ import { normalizeBrowserModelStrategy } from "../browser/modelStrategy.js";
|
|
|
7
7
|
import { getOracleHomeDir } from "../oracleHome.js";
|
|
8
8
|
const DEFAULT_BROWSER_TIMEOUT_MS = 1_200_000;
|
|
9
9
|
const DEFAULT_BROWSER_INPUT_TIMEOUT_MS = 60_000;
|
|
10
|
+
const DEFAULT_BROWSER_ATTACHMENT_TIMEOUT_MS = 45_000;
|
|
10
11
|
const DEFAULT_BROWSER_RECHECK_TIMEOUT_MS = 120_000;
|
|
11
12
|
const DEFAULT_BROWSER_AUTO_REATTACH_TIMEOUT_MS = 120_000;
|
|
12
13
|
const DEFAULT_CHROME_PROFILE = "Default";
|
|
@@ -15,6 +16,7 @@ const DEFAULT_CHROME_PROFILE = "Default";
|
|
|
15
16
|
const BROWSER_MODEL_LABELS = [
|
|
16
17
|
// Most specific first (e.g., "gpt-5.2-thinking" before "gpt-5.2")
|
|
17
18
|
["gpt-5.5-pro", "Pro"],
|
|
19
|
+
["gpt-5.5-instant", "GPT-5.5 Instant"],
|
|
18
20
|
["gpt-5.5", "Thinking 5.5"],
|
|
19
21
|
["gpt-5.4-pro", "Pro"],
|
|
20
22
|
["gpt-5.2-thinking", "GPT-5.2 Thinking"],
|
|
@@ -34,7 +36,10 @@ export function normalizeChatGptModelForBrowser(model) {
|
|
|
34
36
|
if (!normalized.startsWith("gpt-") || normalized.includes("codex")) {
|
|
35
37
|
return model;
|
|
36
38
|
}
|
|
37
|
-
if (normalized === "gpt-5.5-pro" ||
|
|
39
|
+
if (normalized === "gpt-5.5-pro" ||
|
|
40
|
+
normalized === "gpt-5.5-instant" ||
|
|
41
|
+
normalized === "gpt-5.5" ||
|
|
42
|
+
normalized === "gpt-5.4") {
|
|
38
43
|
return normalized;
|
|
39
44
|
}
|
|
40
45
|
// Pro variants: resolve to the latest Pro model in ChatGPT.
|
|
@@ -101,6 +106,9 @@ export async function buildBrowserConfig(options) {
|
|
|
101
106
|
inputTimeoutMs: options.browserInputTimeout
|
|
102
107
|
? parseDuration(options.browserInputTimeout, DEFAULT_BROWSER_INPUT_TIMEOUT_MS)
|
|
103
108
|
: undefined,
|
|
109
|
+
attachmentTimeoutMs: options.browserAttachmentTimeout
|
|
110
|
+
? parseDuration(options.browserAttachmentTimeout, DEFAULT_BROWSER_ATTACHMENT_TIMEOUT_MS)
|
|
111
|
+
: undefined,
|
|
104
112
|
assistantRecheckDelayMs: options.browserRecheckDelay
|
|
105
113
|
? parseDuration(options.browserRecheckDelay, 0)
|
|
106
114
|
: undefined,
|
|
@@ -43,6 +43,9 @@ export function applyBrowserDefaultsFromConfig(options, config, getSource) {
|
|
|
43
43
|
if (isUnset("browserInputTimeout") && typeof browser.inputTimeoutMs === "number") {
|
|
44
44
|
options.browserInputTimeout = String(browser.inputTimeoutMs);
|
|
45
45
|
}
|
|
46
|
+
if (isUnset("browserAttachmentTimeout") && typeof browser.attachmentTimeoutMs === "number") {
|
|
47
|
+
options.browserAttachmentTimeout = String(browser.attachmentTimeoutMs);
|
|
48
|
+
}
|
|
46
49
|
if (isUnset("browserRecheckDelay") && typeof browser.assistantRecheckDelayMs === "number") {
|
|
47
50
|
options.browserRecheckDelay = String(browser.assistantRecheckDelayMs);
|
|
48
51
|
}
|
package/dist/src/cli/engine.js
CHANGED
|
@@ -14,9 +14,10 @@ export function defaultWaitPreference(model, engine) {
|
|
|
14
14
|
* 2) Explicit --engine value.
|
|
15
15
|
* 3) Explicit API provider routing flags force API.
|
|
16
16
|
* 4) ORACLE_ENGINE environment override (api|browser).
|
|
17
|
-
* 5)
|
|
17
|
+
* 5) Config engine value.
|
|
18
|
+
* 6) API environment decides: api when set, otherwise browser.
|
|
18
19
|
*/
|
|
19
|
-
export function resolveEngine({ engine, browserFlag, apiProviderRequested, env, }) {
|
|
20
|
+
export function resolveEngine({ engine, configEngine, browserFlag, apiProviderRequested, env, }) {
|
|
20
21
|
if (browserFlag) {
|
|
21
22
|
return "browser";
|
|
22
23
|
}
|
|
@@ -30,6 +31,9 @@ export function resolveEngine({ engine, browserFlag, apiProviderRequested, env,
|
|
|
30
31
|
if (envEngine) {
|
|
31
32
|
return envEngine;
|
|
32
33
|
}
|
|
34
|
+
if (configEngine) {
|
|
35
|
+
return configEngine;
|
|
36
|
+
}
|
|
33
37
|
return hasApiEnvironment(env) ? "api" : "browser";
|
|
34
38
|
}
|
|
35
39
|
function hasApiEnvironment(env) {
|
package/dist/src/cli/options.js
CHANGED
|
@@ -280,6 +280,10 @@ export function inferModelFromLabel(modelValue) {
|
|
|
280
280
|
if ((normalized.includes("5.5") || normalized.includes("5_5")) && normalized.includes("pro")) {
|
|
281
281
|
return "gpt-5.5-pro";
|
|
282
282
|
}
|
|
283
|
+
if ((normalized.includes("5.5") || normalized.includes("5_5")) &&
|
|
284
|
+
(normalized.includes("instant") || normalized.includes("fast"))) {
|
|
285
|
+
return "gpt-5.5-instant";
|
|
286
|
+
}
|
|
283
287
|
if (normalized.includes("5.5") || normalized.includes("5_5")) {
|
|
284
288
|
return "gpt-5.5";
|
|
285
289
|
}
|