@steipete/oracle 0.20.2 → 0.21.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/dist/bin/oracle-cli.js +14 -42
- package/dist/docs-site/.nojekyll +0 -0
- package/dist/docs-site/CNAME +1 -0
- package/dist/docs-site/RELEASING.html +403 -0
- package/dist/docs-site/advisor.html +317 -0
- package/dist/docs-site/agents.html +375 -0
- package/dist/docs-site/anthropic.html +368 -0
- package/dist/docs-site/bridge.html +421 -0
- package/dist/docs-site/browser-mode.html +636 -0
- package/dist/docs-site/chromium-forks.html +355 -0
- package/dist/docs-site/cli-reference.html +347 -0
- package/dist/docs-site/configuration.html +464 -0
- package/dist/docs-site/favicon.svg +14 -0
- package/dist/docs-site/followup.html +375 -0
- package/dist/docs-site/gemini.html +387 -0
- package/dist/docs-site/grok.html +325 -0
- package/dist/docs-site/index.html +360 -0
- package/dist/docs-site/install.html +335 -0
- package/dist/docs-site/linux.html +321 -0
- package/dist/docs-site/llms.txt +44 -0
- package/dist/docs-site/manual-tests.html +621 -0
- package/dist/docs-site/mcp.html +412 -0
- package/dist/docs-site/multimodel.html +364 -0
- package/dist/docs-site/mythical-pro-agents.html +360 -0
- package/dist/docs-site/notifier.html +338 -0
- package/dist/docs-site/openai-endpoints.html +417 -0
- package/dist/docs-site/openrouter.html +344 -0
- package/dist/docs-site/quickstart.html +369 -0
- package/dist/docs-site/refactor/ux.html +321 -0
- package/dist/docs-site/sessions.html +397 -0
- package/dist/docs-site/social-card.png +0 -0
- package/dist/docs-site/social-card.svg +79 -0
- package/dist/docs-site/spec.html +363 -0
- package/dist/docs-site/testing.html +323 -0
- package/dist/docs-site/tui-debug.html +326 -0
- package/dist/docs-site/windows-work.html +348 -0
- package/dist/docs-site/windows.html +320 -0
- package/dist/src/browser/actions/promptComposer.js +0 -1
- package/dist/src/browser/chatgptConversation.js +309 -0
- package/dist/src/browser/config.js +2 -0
- package/dist/src/browser/configLogging.js +8 -0
- package/dist/src/browser/executor.js +19 -0
- package/dist/src/browser/index.js +80 -241
- package/dist/src/browser/provider.js +11 -0
- package/dist/src/browser/reattach.js +0 -1
- package/dist/src/browser/reattachHelpers.js +14 -15
- package/dist/src/browser/sessionRunner.js +7 -4
- package/dist/src/browser/uiWarnings.js +202 -0
- package/dist/src/cli/bridge/claudeConfig.js +0 -5
- package/dist/src/cli/browserConfig.js +1 -0
- package/dist/src/cli/browserDefaults.js +4 -0
- package/dist/src/cli/runOptions.js +2 -2
- package/dist/src/cli/sessionDisplay.js +0 -1
- package/dist/src/cli/sessionRunner.js +1 -0
- package/dist/src/config.js +1 -0
- package/dist/src/gemini-web/executor.js +11 -7
- package/dist/src/mcp/tools/consult.js +1 -1
- package/dist/src/oracle/background.js +0 -2
- package/dist/src/remote/client.js +8 -0
- package/dist/src/remote/server.js +13 -0
- package/dist/src/version.js +0 -1
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/package.json +7 -10
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/dist/scripts/check.js +0 -21
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { formatTokenCount } from "../oracle/runUtils.js";
|
|
3
3
|
import { formatFinishLine } from "../oracle/finishLine.js";
|
|
4
|
-
import {
|
|
4
|
+
import { resolveLocalBrowserExecutor } from "./executor.js";
|
|
5
5
|
import { DEFAULT_BROWSER_CONFIG } from "./config.js";
|
|
6
|
+
import { redactBrowserConfigForDebugLog } from "./configLogging.js";
|
|
6
7
|
import { assembleBrowserPrompt, cleanupGeneratedBrowserBundles, materializeBrowserFallback, } from "./prompt.js";
|
|
7
8
|
import { BrowserAutomationError, BrowserRunCancelledError } from "../oracle/errors.js";
|
|
8
9
|
import { appendArtifacts, saveBrowserTranscriptArtifact, saveDeepResearchReportArtifact, } from "./artifacts.js";
|
|
@@ -57,7 +58,6 @@ function buildBrowserRunWarnings(args) {
|
|
|
57
58
|
}
|
|
58
59
|
export async function runBrowserSessionExecution({ runOptions, browserConfig, cwd, log, signal }, deps = {}) {
|
|
59
60
|
const assemblePrompt = deps.assemblePrompt ?? assembleBrowserPrompt;
|
|
60
|
-
const executeBrowser = deps.executeBrowser ?? runBrowserMode;
|
|
61
61
|
const persistRuntimeHint = deps.persistRuntimeHint ?? (() => { });
|
|
62
62
|
const inputTimeoutMs = browserConfig.inputTimeoutMs ?? DEFAULT_BROWSER_CONFIG.inputTimeoutMs;
|
|
63
63
|
let preparationAbandoned = false;
|
|
@@ -67,6 +67,7 @@ export async function runBrowserSessionExecution({ runOptions, browserConfig, cw
|
|
|
67
67
|
if (signal?.aborted) {
|
|
68
68
|
throw new BrowserRunCancelledError();
|
|
69
69
|
}
|
|
70
|
+
const executeBrowser = deps.executeBrowser ?? (await resolveLocalBrowserExecutor(runOptions));
|
|
70
71
|
try {
|
|
71
72
|
promptArtifacts = await Promise.race([
|
|
72
73
|
assemblePrompt(runOptions, { cwd }).then(async (artifacts) => {
|
|
@@ -123,9 +124,9 @@ export async function runBrowserSessionExecution({ runOptions, browserConfig, cw
|
|
|
123
124
|
}
|
|
124
125
|
async function executeAssembledBrowserSession({ runOptions, browserConfig, log, promptArtifacts, executeBrowser, persistRuntimeHint, signal, }) {
|
|
125
126
|
if (runOptions.verbose) {
|
|
126
|
-
log(chalk.dim(`[verbose] Browser config: ${JSON.stringify({
|
|
127
|
+
log(chalk.dim(`[verbose] Browser config: ${JSON.stringify(redactBrowserConfigForDebugLog({
|
|
127
128
|
...browserConfig,
|
|
128
|
-
})}`));
|
|
129
|
+
}))}`));
|
|
129
130
|
log(chalk.dim(`[verbose] Browser prompt length: ${promptArtifacts.composerText.length} chars`));
|
|
130
131
|
if (promptArtifacts.attachments.length > 0) {
|
|
131
132
|
const attachmentList = promptArtifacts.attachments
|
|
@@ -190,6 +191,7 @@ async function executeAssembledBrowserSession({ runOptions, browserConfig, log,
|
|
|
190
191
|
try {
|
|
191
192
|
browserResult = await executeBrowser({
|
|
192
193
|
prompt: promptArtifacts.composerText,
|
|
194
|
+
model: runOptions.model,
|
|
193
195
|
attachments: promptArtifacts.attachments,
|
|
194
196
|
fallbackSubmission,
|
|
195
197
|
config: executionBrowserConfig,
|
|
@@ -311,6 +313,7 @@ async function executeAssembledBrowserSession({ runOptions, browserConfig, log,
|
|
|
311
313
|
archive: browserResult.archive,
|
|
312
314
|
modelSelection,
|
|
313
315
|
thinkingSelection,
|
|
316
|
+
providerNativeCapture: browserResult.providerNativeCapture,
|
|
314
317
|
warnings,
|
|
315
318
|
answerText,
|
|
316
319
|
artifacts: savedArtifacts,
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { BrowserAutomationError } from "../oracle/errors.js";
|
|
2
|
+
const MAX_CHATGPT_UI_WARNING_CHARS = 300;
|
|
3
|
+
const MAX_CHATGPT_UI_WARNINGS = 3;
|
|
4
|
+
export function classifyChatGptUiWarningText(text) {
|
|
5
|
+
const normalized = text.toLowerCase();
|
|
6
|
+
if (/\btoo many requests\b/.test(normalized) ||
|
|
7
|
+
/\bsending too many requests\b/.test(normalized) ||
|
|
8
|
+
/\btoo quickly\b/.test(normalized) ||
|
|
9
|
+
/\btemporarily limited access\b/.test(normalized) ||
|
|
10
|
+
/\bplease wait a few minutes\b/.test(normalized) ||
|
|
11
|
+
/\brate limit(?:ed)?\b/.test(normalized) ||
|
|
12
|
+
/\bslow down\b/.test(normalized)) {
|
|
13
|
+
return "rate_limit";
|
|
14
|
+
}
|
|
15
|
+
if (/\btemporarily unavailable\b/.test(normalized) ||
|
|
16
|
+
/\bsomething went wrong\b/.test(normalized) ||
|
|
17
|
+
/\bfailed to generate\b/.test(normalized) ||
|
|
18
|
+
/\btry again later\b/.test(normalized)) {
|
|
19
|
+
return "temporary_unavailable";
|
|
20
|
+
}
|
|
21
|
+
if (/\bverify you are human\b/.test(normalized) ||
|
|
22
|
+
/\bunusual activity\b/.test(normalized) ||
|
|
23
|
+
/\bcloudflare\b/.test(normalized) ||
|
|
24
|
+
/\bchallenge\b/.test(normalized) ||
|
|
25
|
+
/\blogin required\b/.test(normalized) ||
|
|
26
|
+
/\bsign in\b/.test(normalized)) {
|
|
27
|
+
return "auth_or_challenge";
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
function sanitizeChatGptUiWarningText(text) {
|
|
32
|
+
return text
|
|
33
|
+
.replace(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, "[redacted-email]")
|
|
34
|
+
.replace(/\b((?:access|auth|session)[-_ ]?token|token)\s*[:=]\s*["']?[^\s"',;]+/gi, "$1=[redacted]")
|
|
35
|
+
.replace(/\b(?:sk-(?:ant-|or-)?|xai-)[A-Za-z0-9_-]{8,}\b/g, "[redacted-token]");
|
|
36
|
+
}
|
|
37
|
+
function normalizeUiWarningCandidate(value) {
|
|
38
|
+
if (!value || typeof value !== "object")
|
|
39
|
+
return null;
|
|
40
|
+
const candidate = value;
|
|
41
|
+
const text = typeof candidate.text === "string"
|
|
42
|
+
? sanitizeChatGptUiWarningText(candidate.text.replace(/\s+/g, " ").trim())
|
|
43
|
+
: "";
|
|
44
|
+
if (!text)
|
|
45
|
+
return null;
|
|
46
|
+
return {
|
|
47
|
+
text: text.slice(0, MAX_CHATGPT_UI_WARNING_CHARS),
|
|
48
|
+
source: typeof candidate.source === "string" ? candidate.source : null,
|
|
49
|
+
role: typeof candidate.role === "string" ? candidate.role : null,
|
|
50
|
+
ariaLive: typeof candidate.ariaLive === "string" ? candidate.ariaLive : null,
|
|
51
|
+
selector: typeof candidate.selector === "string" ? candidate.selector : null,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export async function collectChatGptUiWarnings(Runtime) {
|
|
55
|
+
try {
|
|
56
|
+
const { result } = await Runtime.evaluate({
|
|
57
|
+
awaitPromise: true,
|
|
58
|
+
returnByValue: true,
|
|
59
|
+
expression: `(() => {
|
|
60
|
+
const warningPattern = /too many requests|sending too many requests|too quickly|temporarily limited access|please wait a few minutes|rate limit|rate limited|slow down|try again later|temporarily unavailable|something went wrong|failed to generate|verify you are human|unusual activity|cloudflare|challenge|login required|sign in/i;
|
|
61
|
+
const selectors = [
|
|
62
|
+
'[role="alert"]',
|
|
63
|
+
'[role="status"]',
|
|
64
|
+
'[role="dialog"]',
|
|
65
|
+
'[aria-live]',
|
|
66
|
+
'[data-testid*="toast" i]',
|
|
67
|
+
'[data-testid*="banner" i]',
|
|
68
|
+
'[data-testid*="error" i]',
|
|
69
|
+
'[class*="toast" i]',
|
|
70
|
+
'[class*="banner" i]'
|
|
71
|
+
];
|
|
72
|
+
const isVisible = (element) => {
|
|
73
|
+
if (!(element instanceof HTMLElement)) return false;
|
|
74
|
+
let current = element;
|
|
75
|
+
while (current) {
|
|
76
|
+
const currentStyle = window.getComputedStyle(current);
|
|
77
|
+
if (
|
|
78
|
+
!currentStyle ||
|
|
79
|
+
currentStyle.display === 'none' ||
|
|
80
|
+
currentStyle.visibility === 'hidden' ||
|
|
81
|
+
currentStyle.visibility === 'collapse' ||
|
|
82
|
+
Number.parseFloat(currentStyle.opacity || '1') === 0
|
|
83
|
+
) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
current = current.parentElement;
|
|
87
|
+
}
|
|
88
|
+
const rect = element.getBoundingClientRect();
|
|
89
|
+
return rect.width > 0 && rect.height > 0;
|
|
90
|
+
};
|
|
91
|
+
const describe = (element, source, selector = null) => ({
|
|
92
|
+
text: (element.innerText || element.textContent || '').replace(/\\s+/g, ' ').trim().slice(0, 1000),
|
|
93
|
+
source,
|
|
94
|
+
selector,
|
|
95
|
+
role: element.getAttribute('role'),
|
|
96
|
+
ariaLive: element.getAttribute('aria-live')
|
|
97
|
+
});
|
|
98
|
+
const out = [];
|
|
99
|
+
const seen = new Set();
|
|
100
|
+
const warningContainers = [];
|
|
101
|
+
const overlapsWarningContainer = (element) => warningContainers.some((container) => (
|
|
102
|
+
container !== element && (container.contains(element) || element.contains(container))
|
|
103
|
+
));
|
|
104
|
+
const add = (element, entry) => {
|
|
105
|
+
if (!entry.text || !warningPattern.test(entry.text)) return;
|
|
106
|
+
const key = entry.text + '|' + (entry.role || '') + '|' + (entry.ariaLive || '');
|
|
107
|
+
if (seen.has(key)) return;
|
|
108
|
+
seen.add(key);
|
|
109
|
+
warningContainers.push(element);
|
|
110
|
+
out.push(entry);
|
|
111
|
+
};
|
|
112
|
+
for (const selector of selectors) {
|
|
113
|
+
if (out.length >= 5) break;
|
|
114
|
+
let elements = [];
|
|
115
|
+
try {
|
|
116
|
+
elements = Array.from(document.querySelectorAll(selector));
|
|
117
|
+
} catch {
|
|
118
|
+
elements = [];
|
|
119
|
+
}
|
|
120
|
+
for (const element of elements) {
|
|
121
|
+
if (out.length >= 5) break;
|
|
122
|
+
if (overlapsWarningContainer(element)) continue;
|
|
123
|
+
if (isVisible(element)) add(element, describe(element, 'selector', selector));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return out.slice(0, 5);
|
|
127
|
+
})()`,
|
|
128
|
+
});
|
|
129
|
+
const rawWarnings = Array.isArray(result?.value) ? result.value : [];
|
|
130
|
+
const warnings = [];
|
|
131
|
+
const seen = new Set();
|
|
132
|
+
for (const raw of rawWarnings) {
|
|
133
|
+
const candidate = normalizeUiWarningCandidate(raw);
|
|
134
|
+
if (!candidate)
|
|
135
|
+
continue;
|
|
136
|
+
const type = classifyChatGptUiWarningText(candidate.text);
|
|
137
|
+
if (!type)
|
|
138
|
+
continue;
|
|
139
|
+
const key = `${type}:${candidate.text}`;
|
|
140
|
+
if (seen.has(key))
|
|
141
|
+
continue;
|
|
142
|
+
seen.add(key);
|
|
143
|
+
warnings.push({
|
|
144
|
+
type,
|
|
145
|
+
message: candidate.text,
|
|
146
|
+
source: candidate.source,
|
|
147
|
+
role: candidate.role,
|
|
148
|
+
ariaLive: candidate.ariaLive,
|
|
149
|
+
selector: candidate.selector,
|
|
150
|
+
});
|
|
151
|
+
if (warnings.length >= MAX_CHATGPT_UI_WARNINGS)
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
return warnings;
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function formatChatGptUiWarningType(type) {
|
|
161
|
+
switch (type) {
|
|
162
|
+
case "rate_limit":
|
|
163
|
+
return "rate-limit";
|
|
164
|
+
case "temporary_unavailable":
|
|
165
|
+
return "temporary-unavailable";
|
|
166
|
+
case "auth_or_challenge":
|
|
167
|
+
return "authentication/challenge";
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async function createChatGptUiWarningError(params) {
|
|
171
|
+
const [uiWarning] = await collectChatGptUiWarnings(params.Runtime);
|
|
172
|
+
if (!uiWarning)
|
|
173
|
+
return null;
|
|
174
|
+
params.logger(`[browser] ChatGPT UI warning detected (${uiWarning.type}): ${uiWarning.message}`);
|
|
175
|
+
return new BrowserAutomationError(`ChatGPT displayed a ${formatChatGptUiWarningType(uiWarning.type)} warning while waiting for ${params.waitTarget}: ${uiWarning.message}`, {
|
|
176
|
+
stage: params.stage,
|
|
177
|
+
code: "chatgpt-ui-warning",
|
|
178
|
+
uiWarning,
|
|
179
|
+
runtime: params.runtime,
|
|
180
|
+
diagnostics: params.diagnostics,
|
|
181
|
+
}, params.cause);
|
|
182
|
+
}
|
|
183
|
+
export async function throwChatGptUiWarningIfPresent(params) {
|
|
184
|
+
const error = await createChatGptUiWarningError(params);
|
|
185
|
+
if (error)
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
export async function createAssistantTimeoutError(params) {
|
|
189
|
+
const warningError = await createChatGptUiWarningError({
|
|
190
|
+
Runtime: params.Runtime,
|
|
191
|
+
logger: params.logger,
|
|
192
|
+
runtime: params.runtime,
|
|
193
|
+
stage: "assistant-timeout",
|
|
194
|
+
waitTarget: "the assistant",
|
|
195
|
+
diagnostics: params.diagnostics,
|
|
196
|
+
cause: params.cause,
|
|
197
|
+
});
|
|
198
|
+
if (!warningError) {
|
|
199
|
+
return new BrowserAutomationError("Assistant response timed out before completion; reattach later to capture the answer.", { stage: "assistant-timeout", runtime: params.runtime, diagnostics: params.diagnostics }, params.cause);
|
|
200
|
+
}
|
|
201
|
+
return warningError;
|
|
202
|
+
}
|
|
@@ -31,16 +31,11 @@ export async function runBridgeClaudeConfig(options) {
|
|
|
31
31
|
}
|
|
32
32
|
export function formatClaudeMcpConfig({ oracleHomeDir, browserProfileDir, remoteHost, remoteToken, includeToken, localBrowser = false, }) {
|
|
33
33
|
const env = {};
|
|
34
|
-
// biome-ignore lint/complexity/useLiteralKeys: env vars are uppercase and include underscores.
|
|
35
34
|
env["ORACLE_ENGINE"] = "browser";
|
|
36
|
-
// biome-ignore lint/complexity/useLiteralKeys: env vars are uppercase and include underscores.
|
|
37
35
|
env["ORACLE_HOME_DIR"] = oracleHomeDir;
|
|
38
|
-
// biome-ignore lint/complexity/useLiteralKeys: env vars are uppercase and include underscores.
|
|
39
36
|
env["ORACLE_BROWSER_PROFILE_DIR"] = browserProfileDir;
|
|
40
37
|
if (remoteHost && !localBrowser) {
|
|
41
|
-
// biome-ignore lint/complexity/useLiteralKeys: env vars are uppercase and include underscores.
|
|
42
38
|
env["ORACLE_REMOTE_HOST"] = remoteHost;
|
|
43
|
-
// biome-ignore lint/complexity/useLiteralKeys: env vars are uppercase and include underscores.
|
|
44
39
|
env["ORACLE_REMOTE_TOKEN"] = includeToken ? (remoteToken ?? "<YOUR_TOKEN>") : "<YOUR_TOKEN>";
|
|
45
40
|
}
|
|
46
41
|
// Claude Code supports project-scoped `.mcp.json` config files:
|
|
@@ -238,6 +238,7 @@ export async function buildBrowserConfig(options) {
|
|
|
238
238
|
? options.browserResearch
|
|
239
239
|
: "off",
|
|
240
240
|
archiveConversations: options.browserArchive,
|
|
241
|
+
captureProviderNative: options.browserCaptureProviderNative,
|
|
241
242
|
};
|
|
242
243
|
}
|
|
243
244
|
function assertBrowserModelAvailable(model, modelStrategy) {
|
|
@@ -111,6 +111,10 @@ export function applyBrowserDefaultsFromConfig(options, config, getSource) {
|
|
|
111
111
|
browser.thinkingTime !== undefined) {
|
|
112
112
|
options.browserThinkingTime = normalizeThinkingTimeLevel(browser.thinkingTime) ?? undefined;
|
|
113
113
|
}
|
|
114
|
+
if (isUnset("browserCaptureProviderNative") &&
|
|
115
|
+
typeof browser.captureProviderNative === "boolean") {
|
|
116
|
+
options.browserCaptureProviderNative = browser.captureProviderNative;
|
|
117
|
+
}
|
|
114
118
|
if (isUnset("browserResearch") && browser.researchMode !== undefined) {
|
|
115
119
|
options.browserResearch = browser.researchMode;
|
|
116
120
|
}
|
|
@@ -2,6 +2,7 @@ import { DEFAULT_MODEL, MODEL_CONFIGS } from "../oracle.js";
|
|
|
2
2
|
import { resolveEngine } from "./engine.js";
|
|
3
3
|
import { normalizeModelOption, inferModelFromLabel, resolveApiModel, normalizeBaseUrl, } from "./options.js";
|
|
4
4
|
import { resolveGeminiModelId } from "../oracle/gemini.js";
|
|
5
|
+
import { resolveBrowserProvider } from "../browser/provider.js";
|
|
5
6
|
import { resolveOverriddenApiModel } from "../oracle/modelResolver.js";
|
|
6
7
|
import { PromptValidationError } from "../oracle/errors.js";
|
|
7
8
|
import { normalizeChatGptModelForBrowser, isGpt6ProAlias } from "./browserConfig.js";
|
|
@@ -44,8 +45,7 @@ export function resolveRunOptionsFromConfig({ prompt, files = [], model, models,
|
|
|
44
45
|
: resolveApiModel(entry))))
|
|
45
46
|
: [apiModel];
|
|
46
47
|
const browserCompatibilityModels = normalizedRequestedModels.length > 0 ? allModels : [browserModel ?? apiModel];
|
|
47
|
-
const
|
|
48
|
-
const hasNonBrowserCompatibleTarget = browserEngineRequested && browserCompatibilityModels.some((m) => !isBrowserCompatible(m));
|
|
48
|
+
const hasNonBrowserCompatibleTarget = browserEngineRequested && browserCompatibilityModels.some((m) => !resolveBrowserProvider(m));
|
|
49
49
|
if (hasNonBrowserCompatibleTarget) {
|
|
50
50
|
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 });
|
|
51
51
|
}
|
|
@@ -482,7 +482,6 @@ export async function attachSession(sessionId, options) {
|
|
|
482
482
|
}
|
|
483
483
|
};
|
|
484
484
|
await printNew();
|
|
485
|
-
// biome-ignore lint/nursery/noUnnecessaryConditions: deliberate infinite poll
|
|
486
485
|
while (true) {
|
|
487
486
|
const latest = await sessionStore.readSession(sessionId);
|
|
488
487
|
if (!latest) {
|
|
@@ -161,6 +161,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
161
161
|
archive: result.archive,
|
|
162
162
|
modelSelection: result.modelSelection,
|
|
163
163
|
thinkingSelection: result.thinkingSelection,
|
|
164
|
+
providerNativeCapture: result.providerNativeCapture,
|
|
164
165
|
warnings: browserWarnings.length > 0 ? browserWarnings : undefined,
|
|
165
166
|
},
|
|
166
167
|
artifacts: mergeArtifacts(sessionMeta.artifacts, mergeArtifacts(result.artifacts, outputArtifacts.artifacts)),
|
package/dist/src/config.js
CHANGED
|
@@ -131,6 +131,7 @@ function sanitizeProjectConfig(config) {
|
|
|
131
131
|
if (config.browser) {
|
|
132
132
|
sanitized.browser = {};
|
|
133
133
|
const browser = config.browser;
|
|
134
|
+
// Full-conversation retention is user-owned; never allow captureProviderNative here.
|
|
134
135
|
const allowedBrowserKeys = [
|
|
135
136
|
"attachRunning",
|
|
136
137
|
"timeoutMs",
|
|
@@ -252,7 +252,7 @@ export function createGeminiWebExecutor(geminiOptions) {
|
|
|
252
252
|
const startTime = Date.now();
|
|
253
253
|
const log = runOptions.log;
|
|
254
254
|
log?.("[gemini-web] Starting Gemini web executor (TypeScript)");
|
|
255
|
-
const model = resolveGeminiWebModel(runOptions.config?.desiredModel, log);
|
|
255
|
+
const model = resolveGeminiWebModel(runOptions.model ?? runOptions.config?.desiredModel, log);
|
|
256
256
|
const generateImagePath = resolveInvocationPath(geminiOptions.generateImage);
|
|
257
257
|
const editImagePath = resolveInvocationPath(geminiOptions.editImage);
|
|
258
258
|
const outputPath = resolveInvocationPath(geminiOptions.outputPath);
|
|
@@ -296,6 +296,7 @@ export function createGeminiWebExecutor(geminiOptions) {
|
|
|
296
296
|
const httpClient = {
|
|
297
297
|
mode: "http",
|
|
298
298
|
execute: async () => {
|
|
299
|
+
runOptions.signal?.throwIfAborted();
|
|
299
300
|
const useNoKeychainPath = Boolean(runOptions.config?.manualLogin);
|
|
300
301
|
const cookieResult = await loadGeminiCookies(runOptions.config, log, {
|
|
301
302
|
preferManualNoKeychain: useNoKeychainPath,
|
|
@@ -315,6 +316,9 @@ export function createGeminiWebExecutor(geminiOptions) {
|
|
|
315
316
|
const timeoutMs = Math.min(configTimeout ?? defaultTimeoutMs, 600_000);
|
|
316
317
|
const controller = new AbortController();
|
|
317
318
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
319
|
+
const deliverySignal = runOptions.signal
|
|
320
|
+
? AbortSignal.any([controller.signal, runOptions.signal])
|
|
321
|
+
: controller.signal;
|
|
318
322
|
let response;
|
|
319
323
|
try {
|
|
320
324
|
if (editImagePath) {
|
|
@@ -324,7 +328,7 @@ export function createGeminiWebExecutor(geminiOptions) {
|
|
|
324
328
|
model,
|
|
325
329
|
cookieMap: cookieResult.cookieMap,
|
|
326
330
|
chatMetadata: null,
|
|
327
|
-
signal:
|
|
331
|
+
signal: deliverySignal,
|
|
328
332
|
allowModelFallback: geminiOptions.allowModelFallback,
|
|
329
333
|
});
|
|
330
334
|
const editPrompt = `Use image generation tool to ${prompt}`;
|
|
@@ -334,7 +338,7 @@ export function createGeminiWebExecutor(geminiOptions) {
|
|
|
334
338
|
model,
|
|
335
339
|
cookieMap: cookieResult.cookieMap,
|
|
336
340
|
chatMetadata: intro.metadata,
|
|
337
|
-
signal:
|
|
341
|
+
signal: deliverySignal,
|
|
338
342
|
allowModelFallback: geminiOptions.allowModelFallback,
|
|
339
343
|
});
|
|
340
344
|
response = {
|
|
@@ -344,7 +348,7 @@ export function createGeminiWebExecutor(geminiOptions) {
|
|
|
344
348
|
image_count: 0,
|
|
345
349
|
};
|
|
346
350
|
const resolvedOutputPath = outputPath ?? generateImagePath ?? "generated.png";
|
|
347
|
-
const imageSave = await saveFirstGeminiImageFromOutput(out, cookieResult.cookieMap, resolvedOutputPath,
|
|
351
|
+
const imageSave = await saveFirstGeminiImageFromOutput(out, cookieResult.cookieMap, resolvedOutputPath, deliverySignal);
|
|
348
352
|
response.has_images = imageSave.saved;
|
|
349
353
|
response.image_count = imageSave.imageCount;
|
|
350
354
|
if (!imageSave.saved) {
|
|
@@ -358,7 +362,7 @@ export function createGeminiWebExecutor(geminiOptions) {
|
|
|
358
362
|
model,
|
|
359
363
|
cookieMap: cookieResult.cookieMap,
|
|
360
364
|
chatMetadata: null,
|
|
361
|
-
signal:
|
|
365
|
+
signal: deliverySignal,
|
|
362
366
|
allowModelFallback: geminiOptions.allowModelFallback,
|
|
363
367
|
});
|
|
364
368
|
response = {
|
|
@@ -367,7 +371,7 @@ export function createGeminiWebExecutor(geminiOptions) {
|
|
|
367
371
|
has_images: false,
|
|
368
372
|
image_count: 0,
|
|
369
373
|
};
|
|
370
|
-
const imageSave = await saveFirstGeminiImageFromOutput(out, cookieResult.cookieMap, generateImagePath,
|
|
374
|
+
const imageSave = await saveFirstGeminiImageFromOutput(out, cookieResult.cookieMap, generateImagePath, deliverySignal);
|
|
371
375
|
response.has_images = imageSave.saved;
|
|
372
376
|
response.image_count = imageSave.imageCount;
|
|
373
377
|
if (!imageSave.saved) {
|
|
@@ -381,7 +385,7 @@ export function createGeminiWebExecutor(geminiOptions) {
|
|
|
381
385
|
model,
|
|
382
386
|
cookieMap: cookieResult.cookieMap,
|
|
383
387
|
chatMetadata: null,
|
|
384
|
-
signal:
|
|
388
|
+
signal: deliverySignal,
|
|
385
389
|
allowModelFallback: geminiOptions.allowModelFallback,
|
|
386
390
|
});
|
|
387
391
|
response = {
|
|
@@ -49,7 +49,7 @@ const consultInputShape = {
|
|
|
49
49
|
engine: z
|
|
50
50
|
.enum(["api", "browser"])
|
|
51
51
|
.optional()
|
|
52
|
-
.describe("Execution engine. `api` uses
|
|
52
|
+
.describe("Execution engine. `api` uses provider APIs. `browser` uses ChatGPT automation or the local Gemini web client according to the model. Picker labels apply to ChatGPT only. When omitted, Oracle follows CLI defaults: config/ORACLE_ENGINE first, then `api` when OPENAI_API_KEY is set, otherwise `browser`."),
|
|
53
53
|
browserModelLabel: z
|
|
54
54
|
.string()
|
|
55
55
|
.optional()
|
|
@@ -64,7 +64,6 @@ async function pollBackgroundResponse(params) {
|
|
|
64
64
|
let response = initialResponse;
|
|
65
65
|
let firstCycle = true;
|
|
66
66
|
let lastStatus = response.status;
|
|
67
|
-
// biome-ignore lint/nursery/noUnnecessaryConditions: intentional polling loop.
|
|
68
67
|
while (true) {
|
|
69
68
|
const status = response.status ?? "completed";
|
|
70
69
|
// firstCycle toggles immediately; keep for clarity in logs.
|
|
@@ -109,7 +108,6 @@ async function pollBackgroundResponse(params) {
|
|
|
109
108
|
async function retrieveBackgroundResponseWithRetry(params) {
|
|
110
109
|
const { client, responseId, wait, now, maxWaitMs, startMark, log } = params;
|
|
111
110
|
let retries = 0;
|
|
112
|
-
// biome-ignore lint/nursery/noUnnecessaryConditions: intentional retry loop
|
|
113
111
|
while (true) {
|
|
114
112
|
try {
|
|
115
113
|
const next = await client.responses.retrieve(responseId);
|
|
@@ -11,9 +11,17 @@ import { checkRemoteHealth } from "./health.js";
|
|
|
11
11
|
import { parseHostPort } from "../bridge/connection.js";
|
|
12
12
|
import { BrowserRunCancelledError } from "../oracle/errors.js";
|
|
13
13
|
import { resolveSiblingImagePath } from "../browser/chatgptImages.js";
|
|
14
|
+
import { resolveBrowserProvider, REMOTE_GEMINI_UNSUPPORTED_MESSAGE } from "../browser/provider.js";
|
|
14
15
|
export function createRemoteBrowserExecutor({ host, token }) {
|
|
15
16
|
// Return a drop-in replacement for runBrowserMode so the browser session runner can stay unchanged.
|
|
16
17
|
return async function remoteBrowserExecutor(options) {
|
|
18
|
+
if (options.model !== undefined && !resolveBrowserProvider(options.model)) {
|
|
19
|
+
throw new Error(`Unsupported browser model: ${options.model}. Remote services support ChatGPT only.`);
|
|
20
|
+
}
|
|
21
|
+
if (resolveBrowserProvider(options.model) === "gemini" ||
|
|
22
|
+
resolveBrowserProvider(options.config?.desiredModel) === "gemini") {
|
|
23
|
+
throw new Error(REMOTE_GEMINI_UNSUPPORTED_MESSAGE);
|
|
24
|
+
}
|
|
17
25
|
if (options.config?.researchMode === "search") {
|
|
18
26
|
throw new Error("Web Search is a local browser pilot; --remote-host does not negotiate this capability yet. Use local Chrome or --browser-attach-running.");
|
|
19
27
|
}
|
|
@@ -19,6 +19,7 @@ import { getCookies } from "@steipete/sweet-cookie";
|
|
|
19
19
|
import { CHATGPT_URL } from "../browser/constants.js";
|
|
20
20
|
import { getCliVersion } from "../version.js";
|
|
21
21
|
import { getOracleHomeDir } from "../oracleHome.js";
|
|
22
|
+
import { resolveBrowserProvider, REMOTE_GEMINI_UNSUPPORTED_MESSAGE } from "../browser/provider.js";
|
|
22
23
|
import { cleanupStaleProfileState, readDevToolsPort, verifyDevToolsReachable, writeChromePid, writeDevToolsActivePort, } from "../browser/profileState.js";
|
|
23
24
|
import { normalizeChatgptUrl } from "../browser/utils.js";
|
|
24
25
|
import { computeFileSha256, resolveSessionArtifactsDir, sanitizeArtifactFilename, sanitizeArtifactMimeType, validateArtifactFile, } from "../browser/artifacts.js";
|
|
@@ -230,6 +231,17 @@ export async function createRemoteServer(options = {}, deps = {}) {
|
|
|
230
231
|
await abandon();
|
|
231
232
|
return;
|
|
232
233
|
}
|
|
234
|
+
if (resolveBrowserProvider(payload.browserConfig?.desiredModel) === "gemini") {
|
|
235
|
+
await abandon();
|
|
236
|
+
if (!res.destroyed) {
|
|
237
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
238
|
+
res.end(JSON.stringify({
|
|
239
|
+
error: "unsupported_browser_provider",
|
|
240
|
+
message: REMOTE_GEMINI_UNSUPPORTED_MESSAGE,
|
|
241
|
+
}));
|
|
242
|
+
}
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
233
245
|
if (!admissionEnabled) {
|
|
234
246
|
signal = payload.options.cancelOnDisconnect === true ? controller.signal : undefined;
|
|
235
247
|
reserve();
|
|
@@ -855,6 +867,7 @@ function sanitizeResult(result, warnings = []) {
|
|
|
855
867
|
answerChars: result.answerChars,
|
|
856
868
|
modelSelection: result.modelSelection,
|
|
857
869
|
thinkingSelection: result.thinkingSelection,
|
|
870
|
+
providerNativeCapture: result.providerNativeCapture,
|
|
858
871
|
researchPlan: result.researchPlan,
|
|
859
872
|
archive: result.archive,
|
|
860
873
|
tabUrl: result.tabUrl,
|
package/dist/src/version.js
CHANGED
|
@@ -13,7 +13,6 @@ function readVersionFromPackage() {
|
|
|
13
13
|
const modulePath = fileURLToPath(import.meta.url);
|
|
14
14
|
let currentDir = path.dirname(modulePath);
|
|
15
15
|
const filesystemRoot = path.parse(currentDir).root;
|
|
16
|
-
// biome-ignore lint/nursery/noUnnecessaryConditions: deliberate sentinel loop to walk up directories
|
|
17
16
|
while (true) {
|
|
18
17
|
const candidate = path.join(currentDir, "package.json");
|
|
19
18
|
try {
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steipete/oracle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "CLI wrapper around OpenAI Responses API with GPT-5.6 Sol, GPT-5.6, GPT-5.5 Pro, GPT-5.5, GPT-5.4, GPT-5.2, GPT-5.1, and GPT-5.1 Codex high reasoning modes.",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"homepage": "https://askoracle.sh",
|
|
@@ -58,8 +58,7 @@
|
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@anthropic-ai/tokenizer": "^0.0.4",
|
|
61
|
-
"@google/genai": "^2.
|
|
62
|
-
"@google/generative-ai": "^0.24.1",
|
|
61
|
+
"@google/genai": "^2.22.0",
|
|
63
62
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
64
63
|
"@steipete/sweet-cookie": "^0.4.3",
|
|
65
64
|
"chalk": "^6.0.0",
|
|
@@ -74,23 +73,21 @@
|
|
|
74
73
|
"json5": "^2.2.3",
|
|
75
74
|
"kleur": "^4.1.5",
|
|
76
75
|
"markdansi": "0.3.3",
|
|
77
|
-
"openai": "^7.
|
|
76
|
+
"openai": "^7.15.0",
|
|
78
77
|
"osc-progress": "^0.3.3",
|
|
79
78
|
"qs": "^6.16.0",
|
|
80
79
|
"shiki": "^4.4.3",
|
|
81
80
|
"toasted-notifier": "^10.1.0",
|
|
82
81
|
"tokentally": "^0.1.6",
|
|
83
|
-
"zod": "^4.6.
|
|
82
|
+
"zod": "^4.6.2"
|
|
84
83
|
},
|
|
85
84
|
"devDependencies": {
|
|
86
|
-
"@anthropic-ai/tokenizer": "^0.0.4",
|
|
87
85
|
"@modelcontextprotocol/client": "^2.0.0",
|
|
88
86
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
89
87
|
"@types/chrome-remote-interface": "^0.34.0",
|
|
90
|
-
"@types/inquirer": "^9.0.10",
|
|
91
88
|
"@types/node": "^26.5.1",
|
|
92
89
|
"@vitest/coverage-v8": "5.0.0",
|
|
93
|
-
"devtools-protocol": "0.0.
|
|
90
|
+
"devtools-protocol": "0.0.1695952",
|
|
94
91
|
"es-toolkit": "^1.52.0",
|
|
95
92
|
"esbuild": "^0.28.2",
|
|
96
93
|
"oxfmt": "0.67.0",
|
|
@@ -98,7 +95,7 @@
|
|
|
98
95
|
"puppeteer-core": "^25.10.0",
|
|
99
96
|
"tsx": "^4.23.13",
|
|
100
97
|
"typescript": "^7.0.2",
|
|
101
|
-
"vitest": "
|
|
98
|
+
"vitest": "5.0.0"
|
|
102
99
|
},
|
|
103
100
|
"devEngines": {
|
|
104
101
|
"runtime": [
|
|
@@ -111,5 +108,5 @@
|
|
|
111
108
|
"engines": {
|
|
112
109
|
"node": ">=24"
|
|
113
110
|
},
|
|
114
|
-
"packageManager": "pnpm@11.
|
|
111
|
+
"packageManager": "pnpm@11.26.0"
|
|
115
112
|
}
|
|
Binary file
|
|
Binary file
|
package/dist/scripts/check.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
import process from "node:process";
|
|
3
|
-
const buildConfig = {
|
|
4
|
-
entrypoints: ["./bin/oracle-cli.js"],
|
|
5
|
-
outdir: "./.bun-check",
|
|
6
|
-
target: "bun",
|
|
7
|
-
minify: false,
|
|
8
|
-
write: false,
|
|
9
|
-
};
|
|
10
|
-
const result = await Bun.build(buildConfig);
|
|
11
|
-
if (!result.success) {
|
|
12
|
-
console.error("Build failed while checking syntax:");
|
|
13
|
-
for (const log of result.logs) {
|
|
14
|
-
console.error(log.message);
|
|
15
|
-
if (log.position) {
|
|
16
|
-
console.error(`\tat ${log.position.file}:${log.position.line}:${log.position.column}`);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
process.exit(1);
|
|
20
|
-
}
|
|
21
|
-
console.log("Syntax OK");
|