@steipete/oracle 0.6.1 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -8
- package/dist/bin/oracle-cli.js +37 -17
- package/dist/src/browser/actions/assistantResponse.js +81 -49
- package/dist/src/browser/actions/attachments.js +37 -3
- package/dist/src/browser/actions/modelSelection.js +94 -5
- package/dist/src/browser/actions/promptComposer.js +22 -14
- package/dist/src/browser/constants.js +6 -2
- package/dist/src/browser/index.js +78 -5
- package/dist/src/browser/prompt.js +30 -6
- package/dist/src/browser/sessionRunner.js +0 -5
- package/dist/src/cli/browserConfig.js +34 -8
- package/dist/src/cli/help.js +3 -3
- package/dist/src/cli/options.js +20 -8
- package/dist/src/cli/runOptions.js +10 -8
- package/dist/src/cli/sessionRunner.js +0 -3
- package/dist/src/gemini-web/client.js +328 -0
- package/dist/src/gemini-web/executor.js +224 -0
- package/dist/src/gemini-web/index.js +1 -0
- package/dist/src/gemini-web/types.js +1 -0
- package/dist/src/mcp/tools/consult.js +4 -1
- package/dist/src/oracle/config.js +1 -1
- package/dist/src/oracle/run.js +15 -4
- package/package.json +17 -17
- package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/Info.plist +0 -20
- package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
- package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +0 -128
- package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.swift +0 -45
- package/dist/vendor/oracle-notifier/oracle-notifier/README.md +0 -24
- package/dist/vendor/oracle-notifier/oracle-notifier/build-notifier.sh +0 -93
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const CHATGPT_URL = 'https://chatgpt.com/';
|
|
2
|
-
export const DEFAULT_MODEL_TARGET = '
|
|
2
|
+
export const DEFAULT_MODEL_TARGET = 'GPT-5.2 Pro';
|
|
3
3
|
export const COOKIE_URLS = ['https://chatgpt.com', 'https://chat.openai.com', 'https://atlas.openai.com'];
|
|
4
4
|
export const INPUT_SELECTORS = [
|
|
5
5
|
'textarea[data-id="prompt-textarea"]',
|
|
@@ -13,13 +13,17 @@ export const INPUT_SELECTORS = [
|
|
|
13
13
|
];
|
|
14
14
|
export const ANSWER_SELECTORS = [
|
|
15
15
|
'article[data-testid^="conversation-turn"][data-message-author-role="assistant"]',
|
|
16
|
+
'article[data-testid^="conversation-turn"][data-turn="assistant"]',
|
|
16
17
|
'article[data-testid^="conversation-turn"] [data-message-author-role="assistant"]',
|
|
18
|
+
'article[data-testid^="conversation-turn"] [data-turn="assistant"]',
|
|
17
19
|
'article[data-testid^="conversation-turn"] .markdown',
|
|
18
20
|
'[data-message-author-role="assistant"] .markdown',
|
|
21
|
+
'[data-turn="assistant"] .markdown',
|
|
19
22
|
'[data-message-author-role="assistant"]',
|
|
23
|
+
'[data-turn="assistant"]',
|
|
20
24
|
];
|
|
21
25
|
export const CONVERSATION_TURN_SELECTOR = 'article[data-testid^="conversation-turn"]';
|
|
22
|
-
export const ASSISTANT_ROLE_SELECTOR = '[data-message-author-role="assistant"]';
|
|
26
|
+
export const ASSISTANT_ROLE_SELECTOR = '[data-message-author-role="assistant"], [data-turn="assistant"]';
|
|
23
27
|
export const CLOUDFLARE_SCRIPT_SELECTOR = 'script[src*="/challenge-platform/"]';
|
|
24
28
|
export const CLOUDFLARE_TITLE = 'just a moment';
|
|
25
29
|
export const PROMPT_PRIMARY_SELECTOR = '#prompt-textarea';
|
|
@@ -285,7 +285,10 @@ export async function runBrowserMode(options) {
|
|
|
285
285
|
logger(`Uploading attachment: ${attachment.displayPath}`);
|
|
286
286
|
await uploadAttachmentFile({ runtime: Runtime, dom: DOM }, attachment, logger);
|
|
287
287
|
}
|
|
288
|
-
|
|
288
|
+
// Scale timeout based on number of files: base 30s + 15s per additional file
|
|
289
|
+
const baseTimeout = config.inputTimeoutMs ?? 30_000;
|
|
290
|
+
const perFileTimeout = 15_000;
|
|
291
|
+
const waitBudget = Math.max(baseTimeout, 30_000) + (submissionAttachments.length - 1) * perFileTimeout;
|
|
289
292
|
await waitForAttachmentCompletion(Runtime, waitBudget, attachmentNames, logger);
|
|
290
293
|
logger('All attachments uploaded');
|
|
291
294
|
}
|
|
@@ -327,10 +330,13 @@ export async function runBrowserMode(options) {
|
|
|
327
330
|
},
|
|
328
331
|
})).catch(() => null);
|
|
329
332
|
answerMarkdown = copiedMarkdown ?? answerText;
|
|
333
|
+
// Helper to normalize text for echo detection (collapse whitespace, lowercase)
|
|
334
|
+
const normalizeForComparison = (text) => text.toLowerCase().replace(/\s+/g, ' ').trim();
|
|
330
335
|
// Final sanity check: ensure we didn't accidentally capture the user prompt instead of the assistant turn.
|
|
331
336
|
const finalSnapshot = await readAssistantSnapshot(Runtime).catch(() => null);
|
|
332
337
|
const finalText = typeof finalSnapshot?.text === 'string' ? finalSnapshot.text.trim() : '';
|
|
333
|
-
if (
|
|
338
|
+
if (!copiedMarkdown &&
|
|
339
|
+
finalText &&
|
|
334
340
|
finalText !== answerMarkdown.trim() &&
|
|
335
341
|
finalText !== promptText.trim() &&
|
|
336
342
|
finalText.length >= answerMarkdown.trim().length) {
|
|
@@ -338,14 +344,26 @@ export async function runBrowserMode(options) {
|
|
|
338
344
|
answerText = finalText;
|
|
339
345
|
answerMarkdown = finalText;
|
|
340
346
|
}
|
|
341
|
-
|
|
347
|
+
// Detect prompt echo using normalized comparison (whitespace-insensitive)
|
|
348
|
+
const normalizedAnswer = normalizeForComparison(answerMarkdown);
|
|
349
|
+
const normalizedPrompt = normalizeForComparison(promptText);
|
|
350
|
+
const promptPrefix = normalizedPrompt.length >= 80
|
|
351
|
+
? normalizedPrompt.slice(0, Math.min(200, normalizedPrompt.length))
|
|
352
|
+
: '';
|
|
353
|
+
const isPromptEcho = normalizedAnswer === normalizedPrompt || (promptPrefix.length > 0 && normalizedAnswer.startsWith(promptPrefix));
|
|
354
|
+
if (isPromptEcho) {
|
|
355
|
+
logger('Detected prompt echo in response; waiting for actual assistant response...');
|
|
342
356
|
const deadline = Date.now() + 8_000;
|
|
343
357
|
let bestText = null;
|
|
344
358
|
let stableCount = 0;
|
|
345
359
|
while (Date.now() < deadline) {
|
|
346
360
|
const snapshot = await readAssistantSnapshot(Runtime).catch(() => null);
|
|
347
361
|
const text = typeof snapshot?.text === 'string' ? snapshot.text.trim() : '';
|
|
348
|
-
|
|
362
|
+
const normalizedText = normalizeForComparison(text);
|
|
363
|
+
const isStillEcho = !text ||
|
|
364
|
+
normalizedText === normalizedPrompt ||
|
|
365
|
+
(promptPrefix.length > 0 && normalizedText.startsWith(promptPrefix));
|
|
366
|
+
if (!isStillEcho) {
|
|
349
367
|
if (!bestText || text.length > bestText.length) {
|
|
350
368
|
bestText = text;
|
|
351
369
|
stableCount = 0;
|
|
@@ -661,7 +679,10 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
661
679
|
logger(`Uploading attachment: ${attachment.displayPath}`);
|
|
662
680
|
await uploadAttachmentViaDataTransfer({ runtime: Runtime, dom: DOM }, attachment, logger);
|
|
663
681
|
}
|
|
664
|
-
|
|
682
|
+
// Scale timeout based on number of files: base 30s + 15s per additional file
|
|
683
|
+
const baseTimeout = config.inputTimeoutMs ?? 30_000;
|
|
684
|
+
const perFileTimeout = 15_000;
|
|
685
|
+
const waitBudget = Math.max(baseTimeout, 30_000) + (submissionAttachments.length - 1) * perFileTimeout;
|
|
665
686
|
await waitForAttachmentCompletion(Runtime, waitBudget, attachmentNames, logger);
|
|
666
687
|
logger('All attachments uploaded');
|
|
667
688
|
}
|
|
@@ -703,6 +724,58 @@ async function runRemoteBrowserMode(promptText, attachments, config, logger, opt
|
|
|
703
724
|
},
|
|
704
725
|
}).catch(() => null);
|
|
705
726
|
answerMarkdown = copiedMarkdown ?? answerText;
|
|
727
|
+
// Helper to normalize text for echo detection (collapse whitespace, lowercase)
|
|
728
|
+
const normalizeForComparison = (text) => text.toLowerCase().replace(/\s+/g, ' ').trim();
|
|
729
|
+
// Final sanity check: ensure we didn't accidentally capture the user prompt instead of the assistant turn.
|
|
730
|
+
const finalSnapshot = await readAssistantSnapshot(Runtime).catch(() => null);
|
|
731
|
+
const finalText = typeof finalSnapshot?.text === 'string' ? finalSnapshot.text.trim() : '';
|
|
732
|
+
if (finalText &&
|
|
733
|
+
finalText !== answerMarkdown.trim() &&
|
|
734
|
+
finalText !== promptText.trim() &&
|
|
735
|
+
finalText.length >= answerMarkdown.trim().length) {
|
|
736
|
+
logger('Refreshed assistant response via final DOM snapshot');
|
|
737
|
+
answerText = finalText;
|
|
738
|
+
answerMarkdown = finalText;
|
|
739
|
+
}
|
|
740
|
+
// Detect prompt echo using normalized comparison (whitespace-insensitive)
|
|
741
|
+
const normalizedAnswer = normalizeForComparison(answerMarkdown);
|
|
742
|
+
const normalizedPrompt = normalizeForComparison(promptText);
|
|
743
|
+
const promptPrefix = normalizedPrompt.length >= 80
|
|
744
|
+
? normalizedPrompt.slice(0, Math.min(200, normalizedPrompt.length))
|
|
745
|
+
: '';
|
|
746
|
+
const isPromptEcho = normalizedAnswer === normalizedPrompt || (promptPrefix.length > 0 && normalizedAnswer.startsWith(promptPrefix));
|
|
747
|
+
if (isPromptEcho) {
|
|
748
|
+
logger('Detected prompt echo in response; waiting for actual assistant response...');
|
|
749
|
+
const deadline = Date.now() + 8_000;
|
|
750
|
+
let bestText = null;
|
|
751
|
+
let stableCount = 0;
|
|
752
|
+
while (Date.now() < deadline) {
|
|
753
|
+
const snapshot = await readAssistantSnapshot(Runtime).catch(() => null);
|
|
754
|
+
const text = typeof snapshot?.text === 'string' ? snapshot.text.trim() : '';
|
|
755
|
+
const normalizedText = normalizeForComparison(text);
|
|
756
|
+
const isStillEcho = !text ||
|
|
757
|
+
normalizedText === normalizedPrompt ||
|
|
758
|
+
(promptPrefix.length > 0 && normalizedText.startsWith(promptPrefix));
|
|
759
|
+
if (!isStillEcho) {
|
|
760
|
+
if (!bestText || text.length > bestText.length) {
|
|
761
|
+
bestText = text;
|
|
762
|
+
stableCount = 0;
|
|
763
|
+
}
|
|
764
|
+
else if (text === bestText) {
|
|
765
|
+
stableCount += 1;
|
|
766
|
+
}
|
|
767
|
+
if (stableCount >= 2) {
|
|
768
|
+
break;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
772
|
+
}
|
|
773
|
+
if (bestText) {
|
|
774
|
+
logger('Recovered assistant response after detecting prompt echo');
|
|
775
|
+
answerText = bestText;
|
|
776
|
+
answerMarkdown = bestText;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
706
779
|
stopThinkingMonitor?.();
|
|
707
780
|
const durationMs = Date.now() - startedAt;
|
|
708
781
|
const answerChars = answerText.length;
|
|
@@ -6,10 +6,32 @@ import { isKnownModel } from '../oracle/modelResolver.js';
|
|
|
6
6
|
import { buildPromptMarkdown } from '../oracle/promptAssembly.js';
|
|
7
7
|
import { buildAttachmentPlan } from './policies.js';
|
|
8
8
|
const DEFAULT_BROWSER_INLINE_CHAR_BUDGET = 60_000;
|
|
9
|
+
const MEDIA_EXTENSIONS = new Set([
|
|
10
|
+
'.mp4', '.mov', '.avi', '.mkv', '.webm', '.m4v',
|
|
11
|
+
'.mp3', '.wav', '.aac', '.flac', '.ogg', '.m4a',
|
|
12
|
+
'.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.svg', '.heic', '.heif',
|
|
13
|
+
'.pdf',
|
|
14
|
+
]);
|
|
15
|
+
export function isMediaFile(filePath) {
|
|
16
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
17
|
+
return MEDIA_EXTENSIONS.has(ext);
|
|
18
|
+
}
|
|
9
19
|
export async function assembleBrowserPrompt(runOptions, deps = {}) {
|
|
10
20
|
const cwd = deps.cwd ?? process.cwd();
|
|
11
21
|
const readFilesFn = deps.readFilesImpl ?? readFiles;
|
|
12
|
-
const
|
|
22
|
+
const allFilePaths = runOptions.file ?? [];
|
|
23
|
+
const textFilePaths = allFilePaths.filter((f) => !isMediaFile(f));
|
|
24
|
+
const mediaFilePaths = allFilePaths.filter((f) => isMediaFile(f));
|
|
25
|
+
const mediaAttachments = await Promise.all(mediaFilePaths.map(async (filePath) => {
|
|
26
|
+
const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath);
|
|
27
|
+
const stats = await fs.stat(resolvedPath);
|
|
28
|
+
return {
|
|
29
|
+
path: resolvedPath,
|
|
30
|
+
displayPath: path.relative(cwd, resolvedPath) || path.basename(resolvedPath),
|
|
31
|
+
sizeBytes: stats.size,
|
|
32
|
+
};
|
|
33
|
+
}));
|
|
34
|
+
const files = await readFilesFn(textFilePaths, { cwd });
|
|
13
35
|
const basePrompt = (runOptions.prompt ?? '').trim();
|
|
14
36
|
const userPrompt = basePrompt;
|
|
15
37
|
const systemPrompt = runOptions.system?.trim() || '';
|
|
@@ -40,9 +62,10 @@ export async function assembleBrowserPrompt(runOptions, deps = {}) {
|
|
|
40
62
|
.filter(Boolean)
|
|
41
63
|
.join('\n\n')
|
|
42
64
|
.trim();
|
|
43
|
-
const attachments = selectedPlan.attachments
|
|
65
|
+
const attachments = [...selectedPlan.attachments, ...mediaAttachments];
|
|
44
66
|
const shouldBundle = selectedPlan.shouldBundle;
|
|
45
67
|
let bundleText = null;
|
|
68
|
+
let bundled = null;
|
|
46
69
|
if (shouldBundle) {
|
|
47
70
|
const bundleDir = await fs.mkdtemp(path.join(os.tmpdir(), 'oracle-browser-bundle-'));
|
|
48
71
|
const bundlePath = path.join(bundleDir, 'attachments-bundle.txt');
|
|
@@ -59,6 +82,8 @@ export async function assembleBrowserPrompt(runOptions, deps = {}) {
|
|
|
59
82
|
displayPath: bundlePath,
|
|
60
83
|
sizeBytes: Buffer.byteLength(bundleText, 'utf8'),
|
|
61
84
|
});
|
|
85
|
+
attachments.push(...mediaAttachments);
|
|
86
|
+
bundled = { originalCount: sections.length, bundlePath };
|
|
62
87
|
}
|
|
63
88
|
const inlineFileCount = selectedPlan.inlineFileCount;
|
|
64
89
|
const modelConfig = isKnownModel(runOptions.model) ? MODEL_CONFIGS[runOptions.model] : MODEL_CONFIGS['gpt-5.1'];
|
|
@@ -85,7 +110,7 @@ export async function assembleBrowserPrompt(runOptions, deps = {}) {
|
|
|
85
110
|
let fallback = null;
|
|
86
111
|
if (attachmentsPolicy === 'auto' && selectedPlan.mode === 'inline' && sections.length > 0) {
|
|
87
112
|
const fallbackComposerText = baseComposerSections.join('\n\n').trim();
|
|
88
|
-
const fallbackAttachments = uploadPlan.attachments
|
|
113
|
+
const fallbackAttachments = [...uploadPlan.attachments, ...mediaAttachments];
|
|
89
114
|
let fallbackBundled = null;
|
|
90
115
|
if (uploadPlan.shouldBundle) {
|
|
91
116
|
const bundleDir = await fs.mkdtemp(path.join(os.tmpdir(), 'oracle-browser-bundle-'));
|
|
@@ -103,6 +128,7 @@ export async function assembleBrowserPrompt(runOptions, deps = {}) {
|
|
|
103
128
|
displayPath: bundlePath,
|
|
104
129
|
sizeBytes: Buffer.byteLength(fallbackBundleText, 'utf8'),
|
|
105
130
|
});
|
|
131
|
+
fallbackAttachments.push(...mediaAttachments);
|
|
106
132
|
fallbackBundled = { originalCount: sections.length, bundlePath };
|
|
107
133
|
}
|
|
108
134
|
fallback = {
|
|
@@ -121,8 +147,6 @@ export async function assembleBrowserPrompt(runOptions, deps = {}) {
|
|
|
121
147
|
attachmentsPolicy,
|
|
122
148
|
attachmentMode: selectedPlan.mode,
|
|
123
149
|
fallback,
|
|
124
|
-
bundled
|
|
125
|
-
? { originalCount: sections.length, bundlePath: attachments[0].displayPath }
|
|
126
|
-
: null,
|
|
150
|
+
bundled,
|
|
127
151
|
};
|
|
128
152
|
}
|
|
@@ -5,11 +5,6 @@ import { runBrowserMode } from '../browserMode.js';
|
|
|
5
5
|
import { assembleBrowserPrompt } from './prompt.js';
|
|
6
6
|
import { BrowserAutomationError } from '../oracle/errors.js';
|
|
7
7
|
export async function runBrowserSessionExecution({ runOptions, browserConfig, cwd, log }, deps = {}) {
|
|
8
|
-
if (runOptions.model.startsWith('gemini')) {
|
|
9
|
-
throw new BrowserAutomationError('Gemini models are not available in browser mode. Re-run with --engine api.', {
|
|
10
|
-
stage: 'preflight',
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
8
|
const assemblePrompt = deps.assemblePrompt ?? assembleBrowserPrompt;
|
|
14
9
|
const executeBrowser = deps.executeBrowser ?? runBrowserMode;
|
|
15
10
|
const promptArtifacts = await assemblePrompt(runOptions, { cwd });
|
|
@@ -6,19 +6,40 @@ const DEFAULT_BROWSER_TIMEOUT_MS = 1_200_000;
|
|
|
6
6
|
const DEFAULT_BROWSER_INPUT_TIMEOUT_MS = 30_000;
|
|
7
7
|
const DEFAULT_CHROME_PROFILE = 'Default';
|
|
8
8
|
const BROWSER_MODEL_LABELS = {
|
|
9
|
-
|
|
10
|
-
'gpt-5
|
|
11
|
-
'gpt-5.1': 'GPT-5.
|
|
12
|
-
'gpt-5.
|
|
13
|
-
'gpt-5.2
|
|
9
|
+
// Browser engine supports GPT-5.2 and GPT-5.2 Pro (legacy/Pro aliases normalize to those targets).
|
|
10
|
+
'gpt-5-pro': 'GPT-5.2 Pro',
|
|
11
|
+
'gpt-5.1-pro': 'GPT-5.2 Pro',
|
|
12
|
+
'gpt-5.1': 'GPT-5.2',
|
|
13
|
+
'gpt-5.2': 'GPT-5.2',
|
|
14
|
+
// ChatGPT UI doesn't expose "instant" as a separate picker option; treat it as GPT-5.2 for browser automation.
|
|
15
|
+
'gpt-5.2-instant': 'GPT-5.2',
|
|
14
16
|
'gpt-5.2-pro': 'GPT-5.2 Pro',
|
|
15
17
|
'gemini-3-pro': 'Gemini 3 Pro',
|
|
16
18
|
};
|
|
19
|
+
export function normalizeChatGptModelForBrowser(model) {
|
|
20
|
+
const normalized = model.toLowerCase();
|
|
21
|
+
if (!normalized.startsWith('gpt-') || normalized.includes('codex')) {
|
|
22
|
+
return model;
|
|
23
|
+
}
|
|
24
|
+
// Pro variants: always resolve to the latest Pro model in ChatGPT.
|
|
25
|
+
if (normalized === 'gpt-5-pro' || normalized === 'gpt-5.1-pro' || normalized.endsWith('-pro')) {
|
|
26
|
+
return 'gpt-5.2-pro';
|
|
27
|
+
}
|
|
28
|
+
// Legacy / UI-mismatch variants: map to the closest ChatGPT picker target.
|
|
29
|
+
if (normalized === 'gpt-5.2-instant') {
|
|
30
|
+
return 'gpt-5.2';
|
|
31
|
+
}
|
|
32
|
+
if (normalized === 'gpt-5.1') {
|
|
33
|
+
return 'gpt-5.2';
|
|
34
|
+
}
|
|
35
|
+
return model;
|
|
36
|
+
}
|
|
17
37
|
export async function buildBrowserConfig(options) {
|
|
18
38
|
const desiredModelOverride = options.browserModelLabel?.trim();
|
|
19
39
|
const normalizedOverride = desiredModelOverride?.toLowerCase() ?? '';
|
|
20
40
|
const baseModel = options.model.toLowerCase();
|
|
21
|
-
const
|
|
41
|
+
const isChatGptModel = baseModel.startsWith('gpt-') && !baseModel.includes('codex');
|
|
42
|
+
const shouldUseOverride = !isChatGptModel && normalizedOverride.length > 0 && normalizedOverride !== baseModel;
|
|
22
43
|
const cookieNames = parseCookieNames(options.browserCookieNames ?? process.env.ORACLE_BROWSER_COOKIE_NAMES);
|
|
23
44
|
const inline = await resolveInlineCookies({
|
|
24
45
|
inlineArg: options.browserInlineCookies,
|
|
@@ -51,7 +72,11 @@ export async function buildBrowserConfig(options) {
|
|
|
51
72
|
keepBrowser: options.browserKeepBrowser ? true : undefined,
|
|
52
73
|
manualLogin: options.browserManualLogin ? true : undefined,
|
|
53
74
|
hideWindow: options.browserHideWindow ? true : undefined,
|
|
54
|
-
desiredModel:
|
|
75
|
+
desiredModel: isChatGptModel
|
|
76
|
+
? mapModelToBrowserLabel(options.model)
|
|
77
|
+
: shouldUseOverride
|
|
78
|
+
? desiredModelOverride
|
|
79
|
+
: mapModelToBrowserLabel(options.model),
|
|
55
80
|
debug: options.verbose ? true : undefined,
|
|
56
81
|
// Allow cookie failures by default so runs can continue without Chrome/Keychain secrets.
|
|
57
82
|
allowCookieErrors: options.browserAllowCookieErrors ?? true,
|
|
@@ -69,7 +94,8 @@ function selectBrowserPort(options) {
|
|
|
69
94
|
return candidate;
|
|
70
95
|
}
|
|
71
96
|
export function mapModelToBrowserLabel(model) {
|
|
72
|
-
|
|
97
|
+
const normalized = normalizeChatGptModelForBrowser(model);
|
|
98
|
+
return BROWSER_MODEL_LABELS[normalized] ?? DEFAULT_MODEL_TARGET;
|
|
73
99
|
}
|
|
74
100
|
export function resolveBrowserModelLabel(input, model) {
|
|
75
101
|
const trimmed = input?.trim?.() ?? '';
|
package/dist/src/cli/help.js
CHANGED
|
@@ -38,7 +38,7 @@ export function applyHelpStyling(program, version, isTty) {
|
|
|
38
38
|
program.addHelpText('after', () => renderHelpFooter(program, colors));
|
|
39
39
|
}
|
|
40
40
|
function renderHelpBanner(version, colors) {
|
|
41
|
-
const subtitle = 'Prompt + files required — GPT-5.
|
|
41
|
+
const subtitle = 'Prompt + files required — GPT-5.2 Pro/GPT-5.2 for tough questions with code/file context.';
|
|
42
42
|
return `${colors.banner(`Oracle CLI v${version}`)} ${colors.subtitle(`— ${subtitle}`)}\n`;
|
|
43
43
|
}
|
|
44
44
|
function renderHelpFooter(program, colors) {
|
|
@@ -51,7 +51,7 @@ function renderHelpFooter(program, colors) {
|
|
|
51
51
|
`${colors.bullet('•')} Best results: 6–30 sentences plus key source files; very short prompts often yield generic answers.`,
|
|
52
52
|
`${colors.bullet('•')} Oracle is one-shot: it does not remember prior runs, so start fresh each time with full context.`,
|
|
53
53
|
`${colors.bullet('•')} Run ${colors.accent('--files-report')} to inspect token spend before hitting the API.`,
|
|
54
|
-
`${colors.bullet('•')} Non-preview runs spawn detached sessions (especially gpt-5.
|
|
54
|
+
`${colors.bullet('•')} Non-preview runs spawn detached sessions (especially gpt-5.2-pro API). If the CLI times out, do not re-run — reattach with ${colors.accent('oracle session <slug>')} to resume/inspect the existing run.`,
|
|
55
55
|
`${colors.bullet('•')} Set a memorable 3–5 word slug via ${colors.accent('--slug "<words>"')} to keep session IDs tidy.`,
|
|
56
56
|
`${colors.bullet('•')} Finished sessions auto-hide preamble logs when reattached; raw timestamps remain in the saved log file.`,
|
|
57
57
|
`${colors.bullet('•')} Need hidden flags? Run ${colors.accent(`${program.name()} --help --verbose`)} to list search/token/browser overrides.`,
|
|
@@ -61,7 +61,7 @@ function renderHelpFooter(program, colors) {
|
|
|
61
61
|
const formatExample = (command, description) => `${colors.command(` ${command}`)}\n${colors.muted(` ${description}`)}`;
|
|
62
62
|
const examples = [
|
|
63
63
|
formatExample(`${program.name()} --render --copy --prompt "Review the TS data layer for schema drift" --file "src/**/*.ts,*/*.test.ts"`, 'Build the bundle, print it, and copy it for manual paste into ChatGPT.'),
|
|
64
|
-
formatExample(`${program.name()} --prompt "Cross-check the data layer assumptions" --models gpt-5.
|
|
64
|
+
formatExample(`${program.name()} --prompt "Cross-check the data layer assumptions" --models gpt-5.2-pro,gemini-3-pro --file "src/**/*.ts"`, 'Run multiple API models in one go and aggregate cost/usage.'),
|
|
65
65
|
formatExample(`${program.name()} status --hours 72 --limit 50`, 'Show sessions from the last 72h (capped at 50 entries).'),
|
|
66
66
|
formatExample(`${program.name()} session <sessionId>`, 'Attach to a running/completed session and stream the saved transcript.'),
|
|
67
67
|
formatExample(`${program.name()} --prompt "Ship review" --slug "release-readiness-audit"`, 'Encourage the model to hand you a 3–5 word slug and pass it along with --slug.'),
|
package/dist/src/cli/options.js
CHANGED
|
@@ -137,6 +137,9 @@ export function resolveApiModel(modelValue) {
|
|
|
137
137
|
if (normalized.includes('5-pro') && !normalized.includes('5.1')) {
|
|
138
138
|
return 'gpt-5-pro';
|
|
139
139
|
}
|
|
140
|
+
if (normalized.includes('5.2') && normalized.includes('pro')) {
|
|
141
|
+
return 'gpt-5.2-pro';
|
|
142
|
+
}
|
|
140
143
|
if (normalized.includes('5.1') && normalized.includes('pro')) {
|
|
141
144
|
return 'gpt-5.1-pro';
|
|
142
145
|
}
|
|
@@ -149,6 +152,9 @@ export function resolveApiModel(modelValue) {
|
|
|
149
152
|
if (normalized.includes('gemini')) {
|
|
150
153
|
return 'gemini-3-pro';
|
|
151
154
|
}
|
|
155
|
+
if (normalized.includes('pro')) {
|
|
156
|
+
return 'gpt-5.2-pro';
|
|
157
|
+
}
|
|
152
158
|
// Passthrough for custom/OpenRouter model IDs.
|
|
153
159
|
return normalized;
|
|
154
160
|
}
|
|
@@ -169,12 +175,6 @@ export function inferModelFromLabel(modelValue) {
|
|
|
169
175
|
if (normalized.includes('claude') && normalized.includes('opus')) {
|
|
170
176
|
return 'claude-4.1-opus';
|
|
171
177
|
}
|
|
172
|
-
if (normalized.includes('5.0') || normalized.includes('5-pro')) {
|
|
173
|
-
return 'gpt-5-pro';
|
|
174
|
-
}
|
|
175
|
-
if (normalized.includes('gpt-5') && normalized.includes('pro') && !normalized.includes('5.1')) {
|
|
176
|
-
return 'gpt-5-pro';
|
|
177
|
-
}
|
|
178
178
|
if (normalized.includes('codex')) {
|
|
179
179
|
return 'gpt-5.1-codex';
|
|
180
180
|
}
|
|
@@ -182,13 +182,25 @@ export function inferModelFromLabel(modelValue) {
|
|
|
182
182
|
return 'gemini-3-pro';
|
|
183
183
|
}
|
|
184
184
|
if (normalized.includes('classic')) {
|
|
185
|
-
return 'gpt-5
|
|
185
|
+
return 'gpt-5-pro';
|
|
186
|
+
}
|
|
187
|
+
if ((normalized.includes('5.2') || normalized.includes('5_2')) && normalized.includes('pro')) {
|
|
188
|
+
return 'gpt-5.2-pro';
|
|
189
|
+
}
|
|
190
|
+
if (normalized.includes('5.0') || normalized.includes('5-pro')) {
|
|
191
|
+
return 'gpt-5-pro';
|
|
192
|
+
}
|
|
193
|
+
if (normalized.includes('gpt-5') &&
|
|
194
|
+
normalized.includes('pro') &&
|
|
195
|
+
!normalized.includes('5.1') &&
|
|
196
|
+
!normalized.includes('5.2')) {
|
|
197
|
+
return 'gpt-5-pro';
|
|
186
198
|
}
|
|
187
199
|
if ((normalized.includes('5.1') || normalized.includes('5_1')) && normalized.includes('pro')) {
|
|
188
200
|
return 'gpt-5.1-pro';
|
|
189
201
|
}
|
|
190
202
|
if (normalized.includes('pro')) {
|
|
191
|
-
return 'gpt-5.
|
|
203
|
+
return 'gpt-5.2-pro';
|
|
192
204
|
}
|
|
193
205
|
if (normalized.includes('5.1') || normalized.includes('5_1')) {
|
|
194
206
|
return 'gpt-5.1';
|
|
@@ -3,6 +3,7 @@ import { resolveEngine } from './engine.js';
|
|
|
3
3
|
import { normalizeModelOption, inferModelFromLabel, resolveApiModel, normalizeBaseUrl } from './options.js';
|
|
4
4
|
import { resolveGeminiModelId } from '../oracle/gemini.js';
|
|
5
5
|
import { PromptValidationError } from '../oracle/errors.js';
|
|
6
|
+
import { normalizeChatGptModelForBrowser } from './browserConfig.js';
|
|
6
7
|
export function resolveRunOptionsFromConfig({ prompt, files = [], model, models, engine, userConfig, env = process.env, }) {
|
|
7
8
|
const resolvedEngine = resolveEngineWithConfig({ engine, configEngine: userConfig?.engine, env });
|
|
8
9
|
const browserRequested = engine === 'browser';
|
|
@@ -10,10 +11,11 @@ export function resolveRunOptionsFromConfig({ prompt, files = [], model, models,
|
|
|
10
11
|
const requestedModelList = Array.isArray(models) ? models : [];
|
|
11
12
|
const normalizedRequestedModels = requestedModelList.map((entry) => normalizeModelOption(entry)).filter(Boolean);
|
|
12
13
|
const cliModelArg = normalizeModelOption(model ?? userConfig?.model) || DEFAULT_MODEL;
|
|
13
|
-
const
|
|
14
|
+
const inferredModel = resolvedEngine === 'browser' && normalizedRequestedModels.length === 0
|
|
14
15
|
? inferModelFromLabel(cliModelArg)
|
|
15
16
|
: resolveApiModel(cliModelArg);
|
|
16
|
-
|
|
17
|
+
// Browser engine maps Pro/legacy aliases to the latest ChatGPT picker targets (GPT-5.2 / GPT-5.2 Pro).
|
|
18
|
+
const resolvedModel = resolvedEngine === 'browser' ? normalizeChatGptModelForBrowser(inferredModel) : inferredModel;
|
|
17
19
|
const isCodex = resolvedModel.startsWith('gpt-5.1-codex');
|
|
18
20
|
const isClaude = resolvedModel.startsWith('claude');
|
|
19
21
|
const isGrok = resolvedModel.startsWith('grok');
|
|
@@ -21,13 +23,13 @@ export function resolveRunOptionsFromConfig({ prompt, files = [], model, models,
|
|
|
21
23
|
const allModels = normalizedRequestedModels.length > 0
|
|
22
24
|
? Array.from(new Set(normalizedRequestedModels.map((entry) => resolveApiModel(entry))))
|
|
23
25
|
: [resolvedModel];
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
const isBrowserCompatible = (m) => m.startsWith('gpt-') || m.startsWith('gemini');
|
|
27
|
+
const hasNonBrowserCompatibleTarget = (browserRequested || browserConfigured) && allModels.some((m) => !isBrowserCompatible(m));
|
|
28
|
+
if (hasNonBrowserCompatibleTarget) {
|
|
29
|
+
throw new PromptValidationError('Browser engine only supports GPT and Gemini models. Re-run with --engine api for Grok, Claude, or other models.', { engine: 'browser', models: allModels });
|
|
27
30
|
}
|
|
28
|
-
const engineCoercedToApi = engineWasBrowser && (
|
|
29
|
-
|
|
30
|
-
const fixedEngine = isGemini || isCodex || isClaude || isGrok || normalizedRequestedModels.length > 0 ? 'api' : resolvedEngine;
|
|
31
|
+
const engineCoercedToApi = engineWasBrowser && (isCodex || isClaude || isGrok);
|
|
32
|
+
const fixedEngine = isCodex || isClaude || isGrok || normalizedRequestedModels.length > 0 ? 'api' : resolvedEngine;
|
|
31
33
|
const promptWithSuffix = userConfig?.promptSuffix && userConfig.promptSuffix.trim().length > 0
|
|
32
34
|
? `${prompt.trim()}\n${userConfig.promptSuffix}`
|
|
33
35
|
: prompt;
|
|
@@ -38,9 +38,6 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
38
38
|
const modelForStatus = runOptions.model ?? sessionMeta.model;
|
|
39
39
|
try {
|
|
40
40
|
if (mode === 'browser') {
|
|
41
|
-
if (runOptions.model.startsWith('gemini')) {
|
|
42
|
-
throw new Error('Gemini models are not available in browser mode. Re-run with --engine api.');
|
|
43
|
-
}
|
|
44
41
|
if (!browserConfig) {
|
|
45
42
|
throw new Error('Missing browser configuration for session.');
|
|
46
43
|
}
|