@steipete/oracle 0.12.1 → 0.14.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 +72 -61
- package/dist/bin/oracle-cli.js +77 -68
- package/dist/bin/oracle-mcp.js +0 -0
- package/dist/docs-site/.nojekyll +0 -0
- package/dist/docs-site/CNAME +1 -0
- package/dist/docs-site/RELEASING.html +410 -0
- package/dist/docs-site/agents.html +374 -0
- package/dist/docs-site/anthropic.html +368 -0
- package/dist/docs-site/bridge.html +400 -0
- package/dist/docs-site/browser-mode.html +593 -0
- package/dist/docs-site/chromium-forks.html +347 -0
- package/dist/docs-site/cli-reference.html +346 -0
- package/dist/docs-site/configuration.html +452 -0
- package/dist/docs-site/favicon.svg +14 -0
- package/dist/docs-site/followup.html +375 -0
- package/dist/docs-site/gemini.html +383 -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 +43 -0
- package/dist/docs-site/manual-tests.html +596 -0
- package/dist/docs-site/mcp.html +391 -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 +387 -0
- package/dist/docs-site/openrouter.html +344 -0
- package/dist/docs-site/quickstart.html +369 -0
- package/dist/docs-site/refactor/ux.html +532 -0
- package/dist/docs-site/sessions.html +388 -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 +320 -0
- package/dist/docs-site/tui-debug.html +326 -0
- package/dist/docs-site/windows-work.html +323 -0
- package/dist/docs-site/windows.html +320 -0
- package/dist/src/browser/actions/assistantResponse.js +91 -9
- package/dist/src/browser/actions/deepResearch.js +180 -52
- package/dist/src/browser/actions/modelSelection.js +111 -24
- package/dist/src/browser/actions/navigation.js +358 -67
- package/dist/src/browser/actions/promptComposer.js +241 -46
- package/dist/src/browser/actions/thinkingTime.js +308 -89
- package/dist/src/browser/artifacts.js +19 -0
- package/dist/src/browser/chatgptFiles.js +797 -0
- package/dist/src/browser/chatgptImages.js +130 -3
- package/dist/src/browser/chromeLifecycle.js +4 -0
- package/dist/src/browser/config.js +4 -0
- package/dist/src/browser/index.js +353 -41
- package/dist/src/browser/pageActions.js +1 -1
- package/dist/src/browser/policies.js +2 -6
- package/dist/src/browser/projectSourcesRunner.js +14 -2
- package/dist/src/browser/prompt.js +11 -15
- package/dist/src/browser/providers/chatgptDomProvider.js +1 -0
- package/dist/src/browser/reattach.js +6 -2
- package/dist/src/browser/reattachability.js +22 -10
- package/dist/src/browser/recoverConversation.js +73 -0
- package/dist/src/browser/sessionRunner.js +4 -1
- package/dist/src/cli/bridge/doctor.js +7 -2
- package/dist/src/cli/browserConfig.js +13 -2
- package/dist/src/cli/browserDefaults.js +3 -0
- package/dist/src/cli/browserTabs.js +129 -54
- package/dist/src/cli/engine.js +6 -2
- package/dist/src/cli/followup.js +72 -0
- package/dist/src/cli/help.js +1 -1
- package/dist/src/cli/options.js +20 -0
- package/dist/src/cli/reattachGuidance.js +8 -0
- package/dist/src/cli/runOptions.js +10 -31
- package/dist/src/cli/sessionCommand.js +4 -0
- package/dist/src/cli/sessionDisplay.js +17 -3
- package/dist/src/cli/sessionLineage.js +7 -4
- package/dist/src/cli/sessionRunner.js +16 -1
- package/dist/src/config.js +164 -9
- package/dist/src/gemini-web/client.js +5 -10
- package/dist/src/gemini-web/executor.js +1 -24
- package/dist/src/gemini-web/models.js +83 -0
- package/dist/src/mcp/server.js +2 -0
- package/dist/src/mcp/tools/chatgptImage.js +127 -0
- package/dist/src/mcp/tools/consult.js +261 -167
- package/dist/src/mcp/types.js +2 -0
- package/dist/src/mcp/utils.js +87 -1
- package/dist/src/oracle/config.js +27 -1
- package/dist/src/oracle/files.js +4 -6
- package/dist/src/oracle/geminiModels.js +2 -0
- package/dist/src/oracle/markdown.js +36 -3
- package/dist/src/oracle/modelResolver.js +21 -13
- package/dist/src/oracle/promptAssembly.js +2 -4
- package/dist/src/oracle/request.js +3 -5
- package/dist/src/oracle/tokenStats.js +5 -1
- package/dist/src/oracle.js +1 -1
- package/dist/src/sessionManager.js +1 -0
- 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 +20 -29
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { getCliVersion } from "../../version.js";
|
|
3
|
-
import { LoggingMessageNotificationParamsSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { LoggingMessageNotificationParamsSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
4
|
import { ensureBrowserAvailable, mapConsultToRunOptions } from "../utils.js";
|
|
5
5
|
import { sessionStore } from "../../sessionStore.js";
|
|
6
6
|
import { resolveRemoteServiceConfig } from "../../remote/remoteServiceConfig.js";
|
|
@@ -88,6 +88,14 @@ const consultInputShape = {
|
|
|
88
88
|
.boolean()
|
|
89
89
|
.optional()
|
|
90
90
|
.describe("Browser-only: keep Chrome running after completion (useful for debugging)."),
|
|
91
|
+
generateImage: z
|
|
92
|
+
.string()
|
|
93
|
+
.optional()
|
|
94
|
+
.describe("Browser-only: save generated image(s) to this file path. For ChatGPT browser mode this enables the image-aware wait/download path used by CLI --generate-image."),
|
|
95
|
+
outputPath: z
|
|
96
|
+
.string()
|
|
97
|
+
.optional()
|
|
98
|
+
.describe("Browser-only image output fallback path, mirroring the CLI --output option for image operations."),
|
|
91
99
|
dryRun: z
|
|
92
100
|
.boolean()
|
|
93
101
|
.optional()
|
|
@@ -130,6 +138,20 @@ const consultModelSummaryShape = z.object({
|
|
|
130
138
|
.optional(),
|
|
131
139
|
logPath: z.string().optional(),
|
|
132
140
|
});
|
|
141
|
+
const consultArtifactSummaryShape = z.object({
|
|
142
|
+
kind: z.enum(["transcript", "deep-research-report", "image", "file"]),
|
|
143
|
+
path: z.string(),
|
|
144
|
+
label: z.string().optional(),
|
|
145
|
+
mimeType: z.string().optional(),
|
|
146
|
+
sizeBytes: z.number().optional(),
|
|
147
|
+
});
|
|
148
|
+
const consultImageSummaryShape = consultArtifactSummaryShape.extend({
|
|
149
|
+
kind: z.literal("image"),
|
|
150
|
+
alt: z.string().optional(),
|
|
151
|
+
width: z.number().optional(),
|
|
152
|
+
height: z.number().optional(),
|
|
153
|
+
fileId: z.string().optional(),
|
|
154
|
+
});
|
|
133
155
|
const consultDryRunResolvedShape = z.object({
|
|
134
156
|
resolvedEngine: z.enum(["api", "browser"]),
|
|
135
157
|
model: z.string(),
|
|
@@ -149,17 +171,20 @@ const consultDryRunResolvedShape = z.object({
|
|
|
149
171
|
manualLogin: z.boolean().optional(),
|
|
150
172
|
profileDir: z.string().nullable().optional(),
|
|
151
173
|
chatgptUrl: z.string().nullable().optional(),
|
|
174
|
+
imageOutputPath: z.string().nullable().optional(),
|
|
152
175
|
})
|
|
153
176
|
.optional(),
|
|
154
177
|
guidance: z.array(z.string()),
|
|
155
178
|
});
|
|
156
|
-
const consultOutputShape = {
|
|
179
|
+
export const consultOutputShape = {
|
|
157
180
|
sessionId: z.string().optional(),
|
|
158
181
|
status: z.string(),
|
|
159
182
|
output: z.string(),
|
|
160
183
|
dryRun: z.boolean().optional(),
|
|
161
184
|
resolved: consultDryRunResolvedShape.optional(),
|
|
162
185
|
models: z.array(consultModelSummaryShape).optional(),
|
|
186
|
+
artifacts: z.array(consultArtifactSummaryShape).optional(),
|
|
187
|
+
images: z.array(consultImageSummaryShape).optional(),
|
|
163
188
|
};
|
|
164
189
|
export function summarizeModelRunsForConsult(runs) {
|
|
165
190
|
if (!runs || runs.length === 0) {
|
|
@@ -191,6 +216,43 @@ export function summarizeModelRunsForConsult(runs) {
|
|
|
191
216
|
};
|
|
192
217
|
});
|
|
193
218
|
}
|
|
219
|
+
function optionalString(value) {
|
|
220
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
221
|
+
}
|
|
222
|
+
function optionalNumber(value) {
|
|
223
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
224
|
+
}
|
|
225
|
+
export function summarizeArtifactsForConsult(artifacts) {
|
|
226
|
+
if (!artifacts || artifacts.length === 0) {
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
return artifacts.map((artifact) => ({
|
|
230
|
+
kind: artifact.kind,
|
|
231
|
+
path: artifact.path,
|
|
232
|
+
label: artifact.label,
|
|
233
|
+
mimeType: artifact.mimeType,
|
|
234
|
+
sizeBytes: artifact.sizeBytes,
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
export function summarizeImageArtifactsForConsult(artifacts) {
|
|
238
|
+
const images = (artifacts ?? [])
|
|
239
|
+
.filter((artifact) => artifact.kind === "image")
|
|
240
|
+
.map((artifact) => {
|
|
241
|
+
const image = artifact;
|
|
242
|
+
return {
|
|
243
|
+
kind: "image",
|
|
244
|
+
path: artifact.path,
|
|
245
|
+
label: artifact.label,
|
|
246
|
+
mimeType: artifact.mimeType,
|
|
247
|
+
sizeBytes: artifact.sizeBytes,
|
|
248
|
+
alt: optionalString(image.alt),
|
|
249
|
+
width: optionalNumber(image.width),
|
|
250
|
+
height: optionalNumber(image.height),
|
|
251
|
+
fileId: optionalString(image.fileId),
|
|
252
|
+
};
|
|
253
|
+
});
|
|
254
|
+
return images.length > 0 ? images : undefined;
|
|
255
|
+
}
|
|
194
256
|
export function buildConsultBrowserConfig({ userConfig, env, runModel, inputModel, browserModelLabel, browserThinkingTime, browserModelStrategy, browserResearchMode, browserArchive, browserKeepBrowser, }) {
|
|
195
257
|
const configuredBrowser = userConfig.browser ?? {};
|
|
196
258
|
const envProfileDir = (env.ORACLE_BROWSER_PROFILE_DIR ?? "").trim();
|
|
@@ -250,6 +312,10 @@ export function buildConsultDryRunResolved({ resolvedEngine, runOptions, browser
|
|
|
250
312
|
if (followUpCount > 0) {
|
|
251
313
|
guidance.push("This is a multi-turn browser consult; all follow-ups stay in one ChatGPT conversation.");
|
|
252
314
|
}
|
|
315
|
+
const imageOutputPath = runOptions.generateImage ?? runOptions.outputPath ?? null;
|
|
316
|
+
if (resolvedEngine === "browser" && imageOutputPath) {
|
|
317
|
+
guidance.push("ChatGPT generated images will use the image-aware wait/download path and return saved files in structuredContent.images.");
|
|
318
|
+
}
|
|
253
319
|
return {
|
|
254
320
|
resolvedEngine,
|
|
255
321
|
model: runOptions.model,
|
|
@@ -269,6 +335,7 @@ export function buildConsultDryRunResolved({ resolvedEngine, runOptions, browser
|
|
|
269
335
|
manualLogin: browserConfig?.manualLogin,
|
|
270
336
|
profileDir: browserConfig?.manualLoginProfileDir ?? null,
|
|
271
337
|
chatgptUrl,
|
|
338
|
+
imageOutputPath,
|
|
272
339
|
}
|
|
273
340
|
: undefined,
|
|
274
341
|
guidance,
|
|
@@ -300,6 +367,9 @@ export function formatConsultDryRunResolved(details) {
|
|
|
300
367
|
if (details.browser.chatgptUrl) {
|
|
301
368
|
lines.push(` ChatGPT URL: ${details.browser.chatgptUrl}`);
|
|
302
369
|
}
|
|
370
|
+
if (details.browser.imageOutputPath) {
|
|
371
|
+
lines.push(` image output: ${details.browser.imageOutputPath}`);
|
|
372
|
+
}
|
|
303
373
|
}
|
|
304
374
|
lines.push(` follow-ups: ${details.followUpCount}`);
|
|
305
375
|
for (const guidance of details.guidance) {
|
|
@@ -307,28 +377,24 @@ export function formatConsultDryRunResolved(details) {
|
|
|
307
377
|
}
|
|
308
378
|
return lines;
|
|
309
379
|
}
|
|
310
|
-
export function
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
const { prompt, files, model, models, engine, search, browserModelLabel, browserAttachments, browserBundleFiles, browserBundleFormat, browserThinkingTime, browserModelStrategy, browserResearchMode, browserArchive, browserFollowUps, browserKeepBrowser, dryRun, slug, } = parsedInput;
|
|
330
|
-
const { config: userConfig } = await loadUserConfig();
|
|
331
|
-
const { runOptions, resolvedEngine } = mapConsultToRunOptions({
|
|
380
|
+
export async function runConsultTool(input, { server }) {
|
|
381
|
+
const textContent = (text) => [{ type: "text", text }];
|
|
382
|
+
let parsedInput;
|
|
383
|
+
try {
|
|
384
|
+
parsedInput = applyConsultPreset(consultInputSchema.parse(input));
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
return {
|
|
388
|
+
isError: true,
|
|
389
|
+
content: textContent(error instanceof Error ? error.message : String(error)),
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
const { prompt, files, model, models, engine, search, browserModelLabel, browserAttachments, browserBundleFiles, browserBundleFormat, browserThinkingTime, browserModelStrategy, browserResearchMode, browserArchive, browserFollowUps, browserKeepBrowser, generateImage, outputPath, dryRun, slug, } = parsedInput;
|
|
393
|
+
const { config: userConfig } = await loadUserConfig();
|
|
394
|
+
let runOptions;
|
|
395
|
+
let resolvedEngine;
|
|
396
|
+
try {
|
|
397
|
+
({ runOptions, resolvedEngine } = mapConsultToRunOptions({
|
|
332
398
|
prompt,
|
|
333
399
|
files: files ?? [],
|
|
334
400
|
model,
|
|
@@ -339,160 +405,188 @@ export function registerConsultTool(server) {
|
|
|
339
405
|
browserBundleFiles,
|
|
340
406
|
browserBundleFormat,
|
|
341
407
|
browserFollowUps,
|
|
408
|
+
generateImage,
|
|
409
|
+
outputPath,
|
|
342
410
|
userConfig,
|
|
343
411
|
env: process.env,
|
|
412
|
+
}));
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
return {
|
|
416
|
+
isError: true,
|
|
417
|
+
content: textContent(error instanceof Error ? error.message : String(error)),
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
const cwd = process.cwd();
|
|
421
|
+
const sendLog = (text, level = "info") => server
|
|
422
|
+
.sendLoggingMessage(LoggingMessageNotificationParamsSchema.parse({
|
|
423
|
+
level,
|
|
424
|
+
data: { text, bytes: Buffer.byteLength(text, "utf8") },
|
|
425
|
+
}))
|
|
426
|
+
.catch(() => { });
|
|
427
|
+
const resolvedRemote = resolveRemoteServiceConfig({ userConfig, env: process.env });
|
|
428
|
+
const imageOutputPath = runOptions.generateImage ?? runOptions.outputPath;
|
|
429
|
+
if (resolvedEngine === "browser" && resolvedRemote.host && imageOutputPath) {
|
|
430
|
+
return {
|
|
431
|
+
isError: true,
|
|
432
|
+
content: textContent("ChatGPT image output is not supported with a remote browser service: generated files are not transferred back to the MCP caller. Unset ORACLE_REMOTE_HOST to generate images locally, or omit generateImage/outputPath."),
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
let browserConfig;
|
|
436
|
+
if (resolvedEngine === "browser") {
|
|
437
|
+
browserConfig = buildConsultBrowserConfig({
|
|
438
|
+
userConfig,
|
|
439
|
+
env: process.env,
|
|
440
|
+
runModel: runOptions.model,
|
|
441
|
+
inputModel: model,
|
|
442
|
+
browserModelLabel,
|
|
443
|
+
browserThinkingTime,
|
|
444
|
+
browserModelStrategy,
|
|
445
|
+
browserResearchMode,
|
|
446
|
+
browserArchive,
|
|
447
|
+
browserKeepBrowser,
|
|
344
448
|
});
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
browserConfig
|
|
356
|
-
userConfig,
|
|
357
|
-
env: process.env,
|
|
358
|
-
runModel: runOptions.model,
|
|
359
|
-
inputModel: model,
|
|
360
|
-
browserModelLabel,
|
|
361
|
-
browserThinkingTime,
|
|
362
|
-
browserModelStrategy,
|
|
363
|
-
browserResearchMode,
|
|
364
|
-
browserArchive,
|
|
365
|
-
browserKeepBrowser,
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
if (dryRun) {
|
|
369
|
-
const lines = [];
|
|
370
|
-
const log = (line) => {
|
|
371
|
-
lines.push(line);
|
|
372
|
-
sendLog(line);
|
|
373
|
-
};
|
|
374
|
-
const resolved = buildConsultDryRunResolved({
|
|
375
|
-
resolvedEngine,
|
|
376
|
-
runOptions,
|
|
377
|
-
browserConfig,
|
|
378
|
-
});
|
|
379
|
-
await runDryRunSummary({
|
|
380
|
-
engine: resolvedEngine,
|
|
381
|
-
runOptions,
|
|
382
|
-
cwd,
|
|
383
|
-
version: getCliVersion(),
|
|
384
|
-
log,
|
|
385
|
-
browserConfig,
|
|
386
|
-
});
|
|
387
|
-
for (const line of formatConsultDryRunResolved(resolved)) {
|
|
388
|
-
log(line);
|
|
389
|
-
}
|
|
390
|
-
const output = lines.join("\n").trim();
|
|
391
|
-
return {
|
|
392
|
-
content: textContent(output),
|
|
393
|
-
structuredContent: {
|
|
394
|
-
status: "dry-run",
|
|
395
|
-
output,
|
|
396
|
-
dryRun: true,
|
|
397
|
-
resolved,
|
|
398
|
-
},
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
const browserGuard = ensureBrowserAvailable(resolvedEngine, {
|
|
402
|
-
remoteHost: resolvedRemote.host,
|
|
449
|
+
}
|
|
450
|
+
if (dryRun) {
|
|
451
|
+
const lines = [];
|
|
452
|
+
const log = (line) => {
|
|
453
|
+
lines.push(line);
|
|
454
|
+
sendLog(line);
|
|
455
|
+
};
|
|
456
|
+
const resolved = buildConsultDryRunResolved({
|
|
457
|
+
resolvedEngine,
|
|
458
|
+
runOptions,
|
|
459
|
+
browserConfig,
|
|
403
460
|
});
|
|
404
|
-
|
|
461
|
+
await runDryRunSummary({
|
|
462
|
+
engine: resolvedEngine,
|
|
463
|
+
runOptions,
|
|
464
|
+
cwd,
|
|
465
|
+
version: getCliVersion(),
|
|
466
|
+
log,
|
|
467
|
+
browserConfig,
|
|
468
|
+
});
|
|
469
|
+
for (const line of formatConsultDryRunResolved(resolved)) {
|
|
470
|
+
log(line);
|
|
471
|
+
}
|
|
472
|
+
const output = lines.join("\n").trim();
|
|
473
|
+
return {
|
|
474
|
+
content: textContent(output),
|
|
475
|
+
structuredContent: {
|
|
476
|
+
status: "dry-run",
|
|
477
|
+
output,
|
|
478
|
+
dryRun: true,
|
|
479
|
+
resolved,
|
|
480
|
+
},
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
const browserGuard = ensureBrowserAvailable(resolvedEngine, {
|
|
484
|
+
remoteHost: resolvedRemote.host,
|
|
485
|
+
});
|
|
486
|
+
if (resolvedEngine === "browser" && browserGuard) {
|
|
487
|
+
return {
|
|
488
|
+
isError: true,
|
|
489
|
+
content: textContent(browserGuard),
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
let browserDeps;
|
|
493
|
+
if (resolvedEngine === "browser" && resolvedRemote.host) {
|
|
494
|
+
if (!resolvedRemote.token) {
|
|
405
495
|
return {
|
|
406
496
|
isError: true,
|
|
407
|
-
content: textContent(
|
|
497
|
+
content: textContent(`Remote host configured (${resolvedRemote.host}) but remote token is missing. Run \`oracle bridge client --connect <...>\` or set ORACLE_REMOTE_TOKEN.`),
|
|
408
498
|
};
|
|
409
499
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
500
|
+
browserDeps = {
|
|
501
|
+
executeBrowser: createRemoteBrowserExecutor({
|
|
502
|
+
host: resolvedRemote.host,
|
|
503
|
+
token: resolvedRemote.token,
|
|
504
|
+
}),
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
const notifications = resolveNotificationSettings({
|
|
508
|
+
cliNotify: undefined,
|
|
509
|
+
cliNotifySound: undefined,
|
|
510
|
+
env: process.env,
|
|
511
|
+
config: userConfig.notify,
|
|
512
|
+
});
|
|
513
|
+
const sessionMeta = await sessionStore.createSession({
|
|
514
|
+
...runOptions,
|
|
515
|
+
mode: resolvedEngine,
|
|
516
|
+
slug,
|
|
517
|
+
browserConfig,
|
|
518
|
+
waitPreference: true,
|
|
519
|
+
}, cwd, notifications);
|
|
520
|
+
const logWriter = sessionStore.createLogWriter(sessionMeta.id);
|
|
521
|
+
// Stream logs to both the session log and MCP logging notifications, but avoid buffering in memory
|
|
522
|
+
const log = (line) => {
|
|
523
|
+
logWriter.logLine(line);
|
|
524
|
+
if (line !== undefined) {
|
|
525
|
+
sendLog(line);
|
|
424
526
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
527
|
+
};
|
|
528
|
+
const write = (chunk) => {
|
|
529
|
+
logWriter.writeChunk(chunk);
|
|
530
|
+
sendLog(chunk, "debug");
|
|
531
|
+
return true;
|
|
532
|
+
};
|
|
533
|
+
try {
|
|
534
|
+
await performSessionRun({
|
|
535
|
+
sessionMeta,
|
|
536
|
+
runOptions,
|
|
433
537
|
mode: resolvedEngine,
|
|
434
|
-
slug,
|
|
435
538
|
browserConfig,
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
539
|
+
cwd,
|
|
540
|
+
log,
|
|
541
|
+
write,
|
|
542
|
+
version: getCliVersion(),
|
|
543
|
+
notifications,
|
|
544
|
+
muteStdout: true,
|
|
545
|
+
browserDeps,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
catch (error) {
|
|
549
|
+
log(`Run failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
550
|
+
return {
|
|
551
|
+
isError: true,
|
|
552
|
+
content: textContent(`Session ${sessionMeta.id} failed: ${error instanceof Error ? error.message : String(error)}`),
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
finally {
|
|
556
|
+
logWriter.stream.end();
|
|
557
|
+
}
|
|
558
|
+
try {
|
|
559
|
+
const finalMeta = (await sessionStore.readSession(sessionMeta.id)) ?? sessionMeta;
|
|
560
|
+
const summary = `Session ${sessionMeta.id} (${finalMeta.status})`;
|
|
561
|
+
const logTail = await readSessionLogTail(sessionMeta.id, 4000);
|
|
562
|
+
const modelsSummary = summarizeModelRunsForConsult(finalMeta.models);
|
|
563
|
+
const artifacts = summarizeArtifactsForConsult(finalMeta.artifacts);
|
|
564
|
+
const images = summarizeImageArtifactsForConsult(finalMeta.artifacts);
|
|
565
|
+
return {
|
|
566
|
+
content: textContent([summary, logTail || "(log empty)"].join("\n").trim()),
|
|
567
|
+
structuredContent: {
|
|
568
|
+
sessionId: sessionMeta.id,
|
|
569
|
+
status: finalMeta.status,
|
|
570
|
+
output: logTail ?? "",
|
|
571
|
+
models: modelsSummary,
|
|
572
|
+
artifacts,
|
|
573
|
+
images,
|
|
574
|
+
},
|
|
445
575
|
};
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
576
|
+
}
|
|
577
|
+
catch (error) {
|
|
578
|
+
return {
|
|
579
|
+
isError: true,
|
|
580
|
+
content: textContent(`Session completed but metadata fetch failed: ${error instanceof Error ? error.message : String(error)}`),
|
|
450
581
|
};
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
notifications,
|
|
462
|
-
muteStdout: true,
|
|
463
|
-
browserDeps,
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
catch (error) {
|
|
467
|
-
log(`Run failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
468
|
-
return {
|
|
469
|
-
isError: true,
|
|
470
|
-
content: textContent(`Session ${sessionMeta.id} failed: ${error instanceof Error ? error.message : String(error)}`),
|
|
471
|
-
};
|
|
472
|
-
}
|
|
473
|
-
finally {
|
|
474
|
-
logWriter.stream.end();
|
|
475
|
-
}
|
|
476
|
-
try {
|
|
477
|
-
const finalMeta = (await sessionStore.readSession(sessionMeta.id)) ?? sessionMeta;
|
|
478
|
-
const summary = `Session ${sessionMeta.id} (${finalMeta.status})`;
|
|
479
|
-
const logTail = await readSessionLogTail(sessionMeta.id, 4000);
|
|
480
|
-
const modelsSummary = summarizeModelRunsForConsult(finalMeta.models);
|
|
481
|
-
return {
|
|
482
|
-
content: textContent([summary, logTail || "(log empty)"].join("\n").trim()),
|
|
483
|
-
structuredContent: {
|
|
484
|
-
sessionId: sessionMeta.id,
|
|
485
|
-
status: finalMeta.status,
|
|
486
|
-
output: logTail ?? "",
|
|
487
|
-
models: modelsSummary,
|
|
488
|
-
},
|
|
489
|
-
};
|
|
490
|
-
}
|
|
491
|
-
catch (error) {
|
|
492
|
-
return {
|
|
493
|
-
isError: true,
|
|
494
|
-
content: textContent(`Session completed but metadata fetch failed: ${error instanceof Error ? error.message : String(error)}`),
|
|
495
|
-
};
|
|
496
|
-
}
|
|
497
|
-
});
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
export function registerConsultTool(server) {
|
|
585
|
+
server.registerTool("consult", {
|
|
586
|
+
title: "Run an oracle session",
|
|
587
|
+
description: 'Run an Oracle session (API or ChatGPT browser automation). Use `files` to attach project context. If `engine` is omitted, Oracle follows CLI defaults: config/ORACLE_ENGINE first, then API when OPENAI_API_KEY is set, otherwise browser. Browser GPT-5.5 Pro consults can take many minutes; use `dryRun:true` first when configuring an agent and inspect `sessions`/`oracle status` before retrying. Browser manual-login uses a private Oracle Chrome profile separate from the user\'s normal Chrome; dry-run output includes first-time setup guidance when that path is active. For browser-based image/file uploads, set `browserAttachments:"always"`. For ChatGPT image generation, set `generateImage` to enable the same image wait/download path as CLI --generate-image and read returned paths from `images`. Browser consults can include `browserFollowUps` for a multi-turn ChatGPT review in one conversation. Sessions are stored under `ORACLE_HOME_DIR` (shared with the CLI).',
|
|
588
|
+
// Cast to any to satisfy SDK typings across differing Zod versions.
|
|
589
|
+
inputSchema: consultInputShape,
|
|
590
|
+
outputSchema: consultOutputShape,
|
|
591
|
+
}, async (input) => runConsultTool(input, { server: server.server }));
|
|
498
592
|
}
|
package/dist/src/mcp/types.js
CHANGED
|
@@ -18,6 +18,8 @@ export const consultInputSchema = z
|
|
|
18
18
|
browserArchive: z.enum(["auto", "always", "never"]).optional(),
|
|
19
19
|
browserFollowUps: z.array(z.string()).optional(),
|
|
20
20
|
browserKeepBrowser: z.boolean().optional(),
|
|
21
|
+
generateImage: z.string().optional(),
|
|
22
|
+
outputPath: z.string().optional(),
|
|
21
23
|
dryRun: z.boolean().optional(),
|
|
22
24
|
search: z.boolean().optional(),
|
|
23
25
|
slug: z.string().optional(),
|
package/dist/src/mcp/utils.js
CHANGED
|
@@ -1,6 +1,80 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
1
3
|
import { resolveRunOptionsFromConfig } from "../cli/runOptions.js";
|
|
4
|
+
import { getOracleHomeDir } from "../oracleHome.js";
|
|
2
5
|
import { Launcher } from "chrome-launcher";
|
|
3
|
-
|
|
6
|
+
const ALLOW_EXTERNAL_OUTPUT_ENV = "ORACLE_MCP_ALLOW_EXTERNAL_OUTPUT";
|
|
7
|
+
const MCP_OUTPUT_DIRNAME = "generated";
|
|
8
|
+
/**
|
|
9
|
+
* Whether MCP callers may write generated images outside the dedicated Oracle
|
|
10
|
+
* output directory. Off by default: MCP clients are less trusted than CLI users.
|
|
11
|
+
*/
|
|
12
|
+
export function isExternalMcpOutputAllowed(env = process.env) {
|
|
13
|
+
const raw = env[ALLOW_EXTERNAL_OUTPUT_ENV]?.trim().toLowerCase();
|
|
14
|
+
return raw === "1" || raw === "true" || raw === "yes" || raw === "on";
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Resolve `target` through symlinks for the portion that exists on disk, then
|
|
18
|
+
* re-append the not-yet-created remainder. A lexical `path.resolve` is not
|
|
19
|
+
* enough: a symlinked parent under the Oracle home (e.g. `~/.oracle/generated`
|
|
20
|
+
* -> `/tmp/evil`) would pass a string-prefix check while the actual write lands
|
|
21
|
+
* outside the boundary. realpath-ing the deepest existing ancestor closes that.
|
|
22
|
+
*/
|
|
23
|
+
function resolveThroughSymlinks(target) {
|
|
24
|
+
const resolved = path.resolve(target);
|
|
25
|
+
let current = resolved;
|
|
26
|
+
const tail = [];
|
|
27
|
+
// Bounded by the number of path segments; dirname() converges to the root.
|
|
28
|
+
for (;;) {
|
|
29
|
+
try {
|
|
30
|
+
const real = fs.realpathSync(current);
|
|
31
|
+
return tail.length ? path.join(real, ...tail) : real;
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (error.code !== "ENOENT") {
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
if (fs.lstatSync(current).isSymbolicLink()) {
|
|
39
|
+
throw new Error(`MCP output path contains an unresolved symlink: ${current}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (lstatError) {
|
|
43
|
+
if (lstatError.code !== "ENOENT") {
|
|
44
|
+
throw lstatError;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const parent = path.dirname(current);
|
|
48
|
+
if (parent === current) {
|
|
49
|
+
return resolved;
|
|
50
|
+
}
|
|
51
|
+
tail.unshift(path.basename(current));
|
|
52
|
+
current = parent;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Constrain an MCP-supplied output path to ORACLE_HOME_DIR/generated and return
|
|
58
|
+
* its canonical absolute form. This keeps agent writes away from Oracle config,
|
|
59
|
+
* session metadata, and browser profile state. Existing symlinks are resolved
|
|
60
|
+
* before the boundary check. Set ORACLE_MCP_ALLOW_EXTERNAL_OUTPUT to opt into
|
|
61
|
+
* arbitrary output paths as an explicit operator decision.
|
|
62
|
+
*/
|
|
63
|
+
export function resolveMcpOutputPath(requestedPath, field, env = process.env) {
|
|
64
|
+
const resolved = path.resolve(requestedPath);
|
|
65
|
+
if (isExternalMcpOutputAllowed(env)) {
|
|
66
|
+
return resolved;
|
|
67
|
+
}
|
|
68
|
+
const oracleHome = resolveThroughSymlinks(getOracleHomeDir());
|
|
69
|
+
const root = path.join(oracleHome, MCP_OUTPUT_DIRNAME);
|
|
70
|
+
const realTarget = resolveThroughSymlinks(resolved);
|
|
71
|
+
if (realTarget.startsWith(`${root}${path.sep}`)) {
|
|
72
|
+
return realTarget;
|
|
73
|
+
}
|
|
74
|
+
throw new Error(`MCP "${field}" must resolve under the generated output directory (${root}); got "${realTarget}". ` +
|
|
75
|
+
`Use a path under that directory, or set ${ALLOW_EXTERNAL_OUTPUT_ENV}=1 to allow external output paths.`);
|
|
76
|
+
}
|
|
77
|
+
export function mapConsultToRunOptions({ prompt, files, model, models, engine, search, browserAttachments, browserBundleFiles, browserBundleFormat, browserFollowUps, generateImage, outputPath, userConfig, env = process.env, }) {
|
|
4
78
|
// Normalize CLI-style inputs through the shared resolver so config/env defaults apply,
|
|
5
79
|
// then overlay MCP-only overrides such as explicit search toggles.
|
|
6
80
|
const mergedModels = Array.isArray(models) && models.length > 0
|
|
@@ -32,6 +106,18 @@ export function mapConsultToRunOptions({ prompt, files, model, models, engine, s
|
|
|
32
106
|
.map((entry) => entry.trim())
|
|
33
107
|
.filter(Boolean);
|
|
34
108
|
}
|
|
109
|
+
const imageOutputPath = generateImage?.trim();
|
|
110
|
+
if (imageOutputPath) {
|
|
111
|
+
result.runOptions.generateImage = resolveMcpOutputPath(imageOutputPath, "generateImage", env);
|
|
112
|
+
}
|
|
113
|
+
const secondaryOutputPath = outputPath?.trim();
|
|
114
|
+
if (secondaryOutputPath) {
|
|
115
|
+
result.runOptions.outputPath = resolveMcpOutputPath(secondaryOutputPath, "outputPath", env);
|
|
116
|
+
}
|
|
117
|
+
if (result.resolvedEngine !== "browser" &&
|
|
118
|
+
(result.runOptions.generateImage || result.runOptions.outputPath)) {
|
|
119
|
+
throw new Error('MCP image output requires engine:"browser".');
|
|
120
|
+
}
|
|
35
121
|
return result;
|
|
36
122
|
}
|
|
37
123
|
export function ensureBrowserAvailable(engine, options) {
|
|
@@ -164,6 +164,32 @@ export const MODEL_CONFIGS = {
|
|
|
164
164
|
supportsBackground: false,
|
|
165
165
|
supportsSearch: true,
|
|
166
166
|
},
|
|
167
|
+
"gemini-3.5-flash": {
|
|
168
|
+
model: "gemini-3.5-flash",
|
|
169
|
+
provider: "google",
|
|
170
|
+
tokenizer: countTokensGpt5Pro,
|
|
171
|
+
inputLimit: 1_048_576,
|
|
172
|
+
pricing: {
|
|
173
|
+
inputPerToken: 1.5 / 1_000_000,
|
|
174
|
+
outputPerToken: 9 / 1_000_000,
|
|
175
|
+
},
|
|
176
|
+
reasoning: null,
|
|
177
|
+
supportsBackground: false,
|
|
178
|
+
supportsSearch: true,
|
|
179
|
+
},
|
|
180
|
+
"gemini-3.1-flash-lite": {
|
|
181
|
+
model: "gemini-3.1-flash-lite",
|
|
182
|
+
provider: "google",
|
|
183
|
+
tokenizer: countTokensGpt5Pro,
|
|
184
|
+
inputLimit: 1_048_576,
|
|
185
|
+
pricing: {
|
|
186
|
+
inputPerToken: 0.25 / 1_000_000,
|
|
187
|
+
outputPerToken: 1.5 / 1_000_000,
|
|
188
|
+
},
|
|
189
|
+
reasoning: null,
|
|
190
|
+
supportsBackground: false,
|
|
191
|
+
supportsSearch: true,
|
|
192
|
+
},
|
|
167
193
|
"gemini-3-pro": {
|
|
168
194
|
model: "gemini-3-pro",
|
|
169
195
|
provider: "google",
|
|
@@ -223,6 +249,6 @@ export const MODEL_CONFIGS = {
|
|
|
223
249
|
};
|
|
224
250
|
export const DEFAULT_SYSTEM_PROMPT = [
|
|
225
251
|
"You are Oracle, a focused one-shot problem solver.",
|
|
226
|
-
"Emphasize direct answers and cite
|
|
252
|
+
"Emphasize direct answers and cite referenced files as path:line or path:line-line when line numbers are available.",
|
|
227
253
|
].join(" ");
|
|
228
254
|
export const TOKENIZER_OPTIONS = { allowedSpecial: "all" };
|