@steipete/oracle 0.17.3 → 0.19.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 +1 -1
- package/dist/bin/oracle-cli.js +75 -64
- package/dist/bin/oracle-mcp.js +0 -0
- package/dist/src/browser/actions/archiveConversation.js +31 -7
- package/dist/src/browser/actions/assistantResponse.js +43 -1
- package/dist/src/browser/actions/attachmentContext.js +239 -0
- package/dist/src/browser/actions/attachmentDataTransfer.js +19 -1
- package/dist/src/browser/actions/attachmentEvidence.js +78 -0
- package/dist/src/browser/actions/attachmentProgress.js +41 -0
- package/dist/src/browser/actions/attachmentPrompt.js +130 -0
- package/dist/src/browser/actions/attachments.js +306 -355
- package/dist/src/browser/actions/deepResearch.js +2 -1
- package/dist/src/browser/actions/modelSelection.js +13 -3
- package/dist/src/browser/actions/navigation.js +1 -1
- package/dist/src/browser/actions/promptComposer.js +361 -186
- package/dist/src/browser/actions/remoteFileTransfer.js +4 -2
- package/dist/src/browser/actions/thinkingStatus.js +2 -5
- package/dist/src/browser/actions/thinkingTime.js +267 -15
- package/dist/src/browser/attachRunning.js +46 -2
- package/dist/src/browser/attachmentValidation.js +53 -0
- package/dist/src/browser/cancellation.js +105 -0
- package/dist/src/browser/chatgptImages.js +2 -0
- package/dist/src/browser/chatgptThrottle.js +104 -0
- package/dist/src/browser/chromeLifecycle.js +267 -43
- package/dist/src/browser/config.js +15 -3
- package/dist/src/browser/detect.js +1 -1
- package/dist/src/browser/index.js +447 -263
- package/dist/src/browser/modelDisplay.js +9 -0
- package/dist/src/browser/pageActions.js +2 -2
- package/dist/src/browser/policies.js +6 -2
- package/dist/src/browser/profileState.js +114 -9
- package/dist/src/browser/projectSourcesRunner.js +88 -17
- package/dist/src/browser/prompt.js +272 -113
- package/dist/src/browser/providers/chatgptDomProvider.js +2 -0
- package/dist/src/browser/reattach.js +14 -4
- package/dist/src/browser/sessionRunner.js +71 -11
- package/dist/src/browser/tabLeaseRegistry.js +414 -38
- package/dist/src/browser/utils.js +20 -2
- package/dist/src/cli/browserConfig.js +40 -15
- package/dist/src/cli/browserDefaults.js +13 -0
- package/dist/src/cli/browserTabs.js +12 -29
- package/dist/src/cli/detachedSession.js +69 -0
- package/dist/src/cli/dryRun.js +58 -47
- package/dist/src/cli/harvestIntegrity.js +140 -0
- package/dist/src/cli/projectSources.js +5 -3
- package/dist/src/cli/serveBrowserConfig.js +24 -0
- package/dist/src/cli/sessionDisplay.js +9 -2
- package/dist/src/cli/sessionRunner.js +91 -7
- package/dist/src/config.js +1 -0
- package/dist/src/gemini-web/client.js +67 -21
- package/dist/src/gemini-web/executor.js +4 -0
- package/dist/src/gemini-web/http.js +15 -0
- package/dist/src/gemini-web/models.js +15 -11
- package/dist/src/mcp/server.js +11 -14
- package/dist/src/mcp/tools/chatgptImage.js +4 -4
- package/dist/src/mcp/tools/consult.js +97 -26
- package/dist/src/mcp/tools/projectSources.js +4 -6
- package/dist/src/mcp/tools/sessionResources.js +1 -1
- package/dist/src/mcp/tools/sessions.js +2 -2
- package/dist/src/mcp/tools/wait.js +182 -0
- package/dist/src/mcp/types.js +7 -0
- package/dist/src/oracle/errors.js +11 -0
- package/dist/src/oracle/providerRoutePlan.js +3 -2
- package/dist/src/remote/client.js +70 -6
- package/dist/src/remote/health.js +6 -2
- package/dist/src/remote/runSlots.js +83 -0
- package/dist/src/remote/server.js +351 -72
- package/dist/src/sessionManager.js +36 -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 +25 -23
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/dist/bin/oracle.js +0 -683
- package/dist/docs-site/.nojekyll +0 -0
- package/dist/docs-site/CNAME +0 -1
- package/dist/docs-site/RELEASING.html +0 -410
- package/dist/docs-site/agents.html +0 -374
- package/dist/docs-site/anthropic.html +0 -368
- package/dist/docs-site/bridge.html +0 -400
- package/dist/docs-site/browser-mode.html +0 -588
- package/dist/docs-site/chromium-forks.html +0 -347
- package/dist/docs-site/cli-reference.html +0 -346
- package/dist/docs-site/configuration.html +0 -436
- package/dist/docs-site/favicon.svg +0 -14
- package/dist/docs-site/followup.html +0 -368
- package/dist/docs-site/gemini.html +0 -379
- package/dist/docs-site/grok.html +0 -325
- package/dist/docs-site/index.html +0 -360
- package/dist/docs-site/install.html +0 -335
- package/dist/docs-site/linux.html +0 -321
- package/dist/docs-site/llms.txt +0 -43
- package/dist/docs-site/manual-tests.html +0 -595
- package/dist/docs-site/mcp.html +0 -368
- package/dist/docs-site/multimodel.html +0 -364
- package/dist/docs-site/mythical-pro-agents.html +0 -360
- package/dist/docs-site/notifier.html +0 -338
- package/dist/docs-site/openai-endpoints.html +0 -387
- package/dist/docs-site/openrouter.html +0 -344
- package/dist/docs-site/quickstart.html +0 -369
- package/dist/docs-site/refactor/ux.html +0 -532
- package/dist/docs-site/sessions.html +0 -388
- package/dist/docs-site/social-card.png +0 -0
- package/dist/docs-site/social-card.svg +0 -79
- package/dist/docs-site/spec.html +0 -363
- package/dist/docs-site/testing.html +0 -320
- package/dist/docs-site/tui-debug.html +0 -326
- package/dist/docs-site/windows-work.html +0 -323
- package/dist/docs-site/windows.html +0 -320
- package/dist/markdansi/types/index.js +0 -4
- package/dist/oracle/bin/oracle-cli.js +0 -472
- package/dist/oracle/src/browser/actions/assistantResponse.js +0 -471
- package/dist/oracle/src/browser/actions/attachments.js +0 -82
- package/dist/oracle/src/browser/actions/modelSelection.js +0 -190
- package/dist/oracle/src/browser/actions/navigation.js +0 -75
- package/dist/oracle/src/browser/actions/promptComposer.js +0 -167
- package/dist/oracle/src/browser/chromeLifecycle.js +0 -104
- package/dist/oracle/src/browser/config.js +0 -33
- package/dist/oracle/src/browser/constants.js +0 -40
- package/dist/oracle/src/browser/cookies.js +0 -210
- package/dist/oracle/src/browser/domDebug.js +0 -36
- package/dist/oracle/src/browser/index.js +0 -331
- package/dist/oracle/src/browser/pageActions.js +0 -5
- package/dist/oracle/src/browser/prompt.js +0 -88
- package/dist/oracle/src/browser/promptSummary.js +0 -20
- package/dist/oracle/src/browser/sessionRunner.js +0 -80
- package/dist/oracle/src/browser/types.js +0 -1
- package/dist/oracle/src/browser/utils.js +0 -62
- package/dist/oracle/src/browserMode.js +0 -1
- package/dist/oracle/src/cli/browserConfig.js +0 -44
- package/dist/oracle/src/cli/dryRun.js +0 -59
- package/dist/oracle/src/cli/engine.js +0 -17
- package/dist/oracle/src/cli/errorUtils.js +0 -9
- package/dist/oracle/src/cli/help.js +0 -70
- package/dist/oracle/src/cli/markdownRenderer.js +0 -15
- package/dist/oracle/src/cli/options.js +0 -103
- package/dist/oracle/src/cli/promptRequirement.js +0 -14
- package/dist/oracle/src/cli/rootAlias.js +0 -30
- package/dist/oracle/src/cli/sessionCommand.js +0 -77
- package/dist/oracle/src/cli/sessionDisplay.js +0 -270
- package/dist/oracle/src/cli/sessionRunner.js +0 -94
- package/dist/oracle/src/heartbeat.js +0 -43
- package/dist/oracle/src/oracle/client.js +0 -48
- package/dist/oracle/src/oracle/config.js +0 -29
- package/dist/oracle/src/oracle/errors.js +0 -101
- package/dist/oracle/src/oracle/files.js +0 -220
- package/dist/oracle/src/oracle/format.js +0 -33
- package/dist/oracle/src/oracle/fsAdapter.js +0 -7
- package/dist/oracle/src/oracle/oscProgress.js +0 -60
- package/dist/oracle/src/oracle/request.js +0 -48
- package/dist/oracle/src/oracle/run.js +0 -444
- package/dist/oracle/src/oracle/tokenStats.js +0 -39
- package/dist/oracle/src/oracle/types.js +0 -1
- package/dist/oracle/src/oracle.js +0 -9
- package/dist/oracle/src/sessionManager.js +0 -205
- package/dist/oracle/src/version.js +0 -39
- package/dist/scripts/chrome/browser-tools.js +0 -295
- package/dist/src/browser/chromeCookies.js +0 -312
- package/dist/src/browser/keytarShim.js +0 -56
- package/dist/src/browser/profileSync.js +0 -141
- package/dist/src/browser/windowsCookies.js +0 -219
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { getCliVersion } from "../../version.js";
|
|
3
|
-
import { LoggingMessageNotificationParamsSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
3
|
import { ensureBrowserAvailable, mapConsultToRunOptions } from "../utils.js";
|
|
5
4
|
import { sessionStore } from "../../sessionStore.js";
|
|
6
5
|
import { resolveRemoteServiceConfig } from "../../remote/remoteServiceConfig.js";
|
|
7
6
|
import { createRemoteBrowserExecutor } from "../../remote/client.js";
|
|
8
|
-
async function readSessionLogTail(sessionId, maxBytes) {
|
|
7
|
+
export async function readSessionLogTail(sessionId, maxBytes) {
|
|
9
8
|
try {
|
|
10
9
|
const log = await sessionStore.readLog(sessionId);
|
|
11
10
|
if (log.length <= maxBytes) {
|
|
@@ -24,9 +23,11 @@ import { CONSULT_PRESETS, browserThinkingTimeRawSchema, consultInputSchema } fro
|
|
|
24
23
|
import { applyConsultPreset } from "../consultPresets.js";
|
|
25
24
|
import { loadUserConfig } from "../../config.js";
|
|
26
25
|
import { resolveNotificationSettings } from "../../cli/notifier.js";
|
|
27
|
-
import { mapModelToBrowserLabel, resolveBrowserModelLabel } from "../../cli/browserConfig.js";
|
|
26
|
+
import { mapModelToBrowserLabel, resolveBrowserModelLabel, resolveDefaultBrowserThinkingTime, } from "../../cli/browserConfig.js";
|
|
28
27
|
import { normalizeThinkingTimeLevel } from "../../oracle/thinkingTime.js";
|
|
29
|
-
|
|
28
|
+
import { launchDetachedSession } from "../../cli/detachedSession.js";
|
|
29
|
+
import { buildSessionLifecycle } from "../../cli/sessionLifecycle.js";
|
|
30
|
+
// Shared fields are composed into explicit schemas when registering tools.
|
|
30
31
|
const consultInputShape = {
|
|
31
32
|
preset: z
|
|
32
33
|
.enum(CONSULT_PRESETS)
|
|
@@ -60,11 +61,11 @@ const consultInputShape = {
|
|
|
60
61
|
browserBundleFiles: z
|
|
61
62
|
.boolean()
|
|
62
63
|
.optional()
|
|
63
|
-
.describe("Browser-only: bundle
|
|
64
|
+
.describe("Browser-only: force one upload bundle; auto/zip includes all resolved files. Multiple text/source uploads already bundle by default (flattened text unless ZIP is selected)."),
|
|
64
65
|
browserBundleFormat: z
|
|
65
66
|
.enum(["auto", "text", "zip"])
|
|
66
67
|
.optional()
|
|
67
|
-
.describe('Browser-only: bundle upload format
|
|
68
|
+
.describe('Browser-only: bundle upload format. Defaults to "auto", which keeps flattened text for text-only uploads and uses ZIP when raw files are present; "zip" forces a byte-preserving archive.'),
|
|
68
69
|
browserThinkingTime: browserThinkingTimeRawSchema
|
|
69
70
|
.optional()
|
|
70
71
|
.describe("Browser-only: set ChatGPT thinking time when supported by the chosen model."),
|
|
@@ -96,6 +97,10 @@ const consultInputShape = {
|
|
|
96
97
|
.string()
|
|
97
98
|
.optional()
|
|
98
99
|
.describe("Browser-only image output fallback path, mirroring the CLI --output option for image operations."),
|
|
100
|
+
waitForCompletion: z
|
|
101
|
+
.boolean()
|
|
102
|
+
.optional()
|
|
103
|
+
.describe("When false, start the Oracle run in a detached worker and return its sessionId immediately. Use the wait tool to block for completion without agent-side polling. Defaults to true for compatibility."),
|
|
99
104
|
dryRun: z
|
|
100
105
|
.boolean()
|
|
101
106
|
.optional()
|
|
@@ -109,7 +114,7 @@ const consultInputShape = {
|
|
|
109
114
|
.optional()
|
|
110
115
|
.describe("Optional human-friendly session id (used for later `oracle sessions` lookups)."),
|
|
111
116
|
};
|
|
112
|
-
const consultModelSummaryShape = z.object({
|
|
117
|
+
export const consultModelSummaryShape = z.object({
|
|
113
118
|
model: z.string(),
|
|
114
119
|
status: z.string(),
|
|
115
120
|
startedAt: z.string().optional(),
|
|
@@ -138,14 +143,14 @@ const consultModelSummaryShape = z.object({
|
|
|
138
143
|
.optional(),
|
|
139
144
|
logPath: z.string().optional(),
|
|
140
145
|
});
|
|
141
|
-
const consultArtifactSummaryShape = z.object({
|
|
146
|
+
export const consultArtifactSummaryShape = z.object({
|
|
142
147
|
kind: z.enum(["transcript", "deep-research-report", "image", "file"]),
|
|
143
148
|
path: z.string(),
|
|
144
149
|
label: z.string().optional(),
|
|
145
150
|
mimeType: z.string().optional(),
|
|
146
151
|
sizeBytes: z.number().optional(),
|
|
147
152
|
});
|
|
148
|
-
const consultImageSummaryShape = consultArtifactSummaryShape.extend({
|
|
153
|
+
export const consultImageSummaryShape = consultArtifactSummaryShape.extend({
|
|
149
154
|
kind: z.literal("image"),
|
|
150
155
|
alt: z.string().optional(),
|
|
151
156
|
width: z.number().optional(),
|
|
@@ -185,6 +190,7 @@ export const consultOutputShape = {
|
|
|
185
190
|
models: z.array(consultModelSummaryShape).optional(),
|
|
186
191
|
artifacts: z.array(consultArtifactSummaryShape).optional(),
|
|
187
192
|
images: z.array(consultImageSummaryShape).optional(),
|
|
193
|
+
detached: z.boolean().optional(),
|
|
188
194
|
};
|
|
189
195
|
export function summarizeModelRunsForConsult(runs) {
|
|
190
196
|
if (!runs || runs.length === 0) {
|
|
@@ -267,11 +273,14 @@ export function buildConsultBrowserConfig({ userConfig, env, runModel, inputMode
|
|
|
267
273
|
? true
|
|
268
274
|
: (configuredBrowser.manualLogin ?? process.platform === "win32");
|
|
269
275
|
const configuredThinkingTime = normalizeThinkingTimeLevel(configuredBrowser.thinkingTime);
|
|
276
|
+
const modelStrategy = browserModelStrategy ?? configuredBrowser.modelStrategy;
|
|
270
277
|
return {
|
|
271
278
|
...configuredBrowser,
|
|
272
279
|
url: configuredUrl,
|
|
273
280
|
chatgptUrl: configuredUrl,
|
|
274
|
-
cookieSync:
|
|
281
|
+
cookieSync: manualLogin
|
|
282
|
+
? configuredBrowser.manualLoginCookieSync === true
|
|
283
|
+
: configuredBrowser.cookieSync === true,
|
|
275
284
|
headless: configuredBrowser.headless ?? false,
|
|
276
285
|
hideWindow: configuredBrowser.hideWindow ?? false,
|
|
277
286
|
keepBrowser: browserKeepBrowser ?? configuredBrowser.keepBrowser ?? false,
|
|
@@ -279,8 +288,14 @@ export function buildConsultBrowserConfig({ userConfig, env, runModel, inputMode
|
|
|
279
288
|
manualLoginProfileDir: manualLogin
|
|
280
289
|
? ((envProfileDir || configuredBrowser.manualLoginProfileDir) ?? null)
|
|
281
290
|
: null,
|
|
282
|
-
thinkingTime: browserThinkingTime ??
|
|
283
|
-
|
|
291
|
+
thinkingTime: browserThinkingTime ??
|
|
292
|
+
configuredThinkingTime ??
|
|
293
|
+
resolveDefaultBrowserThinkingTime({
|
|
294
|
+
model: runModel,
|
|
295
|
+
requestedModel: inputModel,
|
|
296
|
+
modelStrategy,
|
|
297
|
+
}),
|
|
298
|
+
modelStrategy,
|
|
284
299
|
researchMode: browserResearchMode ?? configuredBrowser.researchMode,
|
|
285
300
|
archiveConversations: browserArchive ?? configuredBrowser.archiveConversations,
|
|
286
301
|
desiredModel: desiredModelLabel || mapModelToBrowserLabel(runModel),
|
|
@@ -378,7 +393,7 @@ export function formatConsultDryRunResolved(details) {
|
|
|
378
393
|
}
|
|
379
394
|
return lines;
|
|
380
395
|
}
|
|
381
|
-
export async function runConsultTool(input, {
|
|
396
|
+
export async function runConsultTool(input, { log: requestLog, launchDetached = launchDetachedSession, }) {
|
|
382
397
|
const textContent = (text) => [{ type: "text", text }];
|
|
383
398
|
let parsedInput;
|
|
384
399
|
try {
|
|
@@ -390,7 +405,7 @@ export async function runConsultTool(input, { server }) {
|
|
|
390
405
|
content: textContent(error instanceof Error ? error.message : String(error)),
|
|
391
406
|
};
|
|
392
407
|
}
|
|
393
|
-
const { prompt, files, model, models, engine, search, browserModelLabel, browserAttachments, browserBundleFiles, browserBundleFormat, browserThinkingTime, browserModelStrategy, browserResearchMode, browserArchive, browserFollowUps, browserKeepBrowser, generateImage, outputPath, dryRun, slug, } = parsedInput;
|
|
408
|
+
const { prompt, files, model, models, engine, search, browserModelLabel, browserAttachments, browserBundleFiles, browserBundleFormat, browserThinkingTime, browserModelStrategy, browserResearchMode, browserArchive, browserFollowUps, browserKeepBrowser, generateImage, outputPath, waitForCompletion = true, dryRun, slug, } = parsedInput;
|
|
394
409
|
const { config: userConfig } = await loadUserConfig();
|
|
395
410
|
let runOptions;
|
|
396
411
|
let resolvedEngine;
|
|
@@ -419,12 +434,7 @@ export async function runConsultTool(input, { server }) {
|
|
|
419
434
|
};
|
|
420
435
|
}
|
|
421
436
|
const cwd = process.cwd();
|
|
422
|
-
const sendLog = (text, level = "info") =>
|
|
423
|
-
.sendLoggingMessage(LoggingMessageNotificationParamsSchema.parse({
|
|
424
|
-
level,
|
|
425
|
-
data: { text, bytes: Buffer.byteLength(text, "utf8") },
|
|
426
|
-
}))
|
|
427
|
-
.catch(() => { });
|
|
437
|
+
const sendLog = (text, level = "info") => requestLog(level, { text, bytes: Buffer.byteLength(text, "utf8") }).catch(() => { });
|
|
428
438
|
const resolvedRemote = resolveRemoteServiceConfig({ userConfig, env: process.env });
|
|
429
439
|
const imageOutputPath = runOptions.generateImage ?? runOptions.outputPath;
|
|
430
440
|
if (resolvedEngine === "browser" && resolvedRemote.host && imageOutputPath) {
|
|
@@ -481,6 +491,18 @@ export async function runConsultTool(input, { server }) {
|
|
|
481
491
|
},
|
|
482
492
|
};
|
|
483
493
|
}
|
|
494
|
+
if (!waitForCompletion && resolvedEngine === "browser" && resolvedRemote.host) {
|
|
495
|
+
return {
|
|
496
|
+
isError: true,
|
|
497
|
+
content: textContent("Detached MCP consults are not supported with a remote browser service yet. Keep waitForCompletion:true for this run."),
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
if (!waitForCompletion && process.env.ORACLE_NO_DETACH === "1") {
|
|
501
|
+
return {
|
|
502
|
+
isError: true,
|
|
503
|
+
content: textContent("Detached MCP consults are disabled by ORACLE_NO_DETACH=1. Remove it or keep waitForCompletion:true."),
|
|
504
|
+
};
|
|
505
|
+
}
|
|
484
506
|
const browserGuard = ensureBrowserAvailable(resolvedEngine, {
|
|
485
507
|
remoteHost: resolvedRemote.host,
|
|
486
508
|
});
|
|
@@ -516,8 +538,58 @@ export async function runConsultTool(input, { server }) {
|
|
|
516
538
|
mode: resolvedEngine,
|
|
517
539
|
slug,
|
|
518
540
|
browserConfig,
|
|
519
|
-
waitPreference:
|
|
541
|
+
waitPreference: waitForCompletion,
|
|
520
542
|
}, cwd, notifications);
|
|
543
|
+
if (!waitForCompletion) {
|
|
544
|
+
try {
|
|
545
|
+
await launchDetached({
|
|
546
|
+
sessionId: sessionMeta.id,
|
|
547
|
+
prepare: async (workerPid) => {
|
|
548
|
+
const lifecycle = buildSessionLifecycle({
|
|
549
|
+
engine: resolvedEngine,
|
|
550
|
+
detached: true,
|
|
551
|
+
workerPid,
|
|
552
|
+
reattachCommand: `oracle session ${sessionMeta.id}`,
|
|
553
|
+
});
|
|
554
|
+
await sessionStore.updateSession(sessionMeta.id, {
|
|
555
|
+
status: "running",
|
|
556
|
+
startedAt: new Date().toISOString(),
|
|
557
|
+
lifecycle,
|
|
558
|
+
});
|
|
559
|
+
},
|
|
560
|
+
});
|
|
561
|
+
const started = (await sessionStore.readSession(sessionMeta.id)) ?? sessionMeta;
|
|
562
|
+
const summary = `Session ${sessionMeta.id} (${started.status}; detached)`;
|
|
563
|
+
return {
|
|
564
|
+
content: textContent(`${summary}\nUse the wait tool with id=${JSON.stringify(sessionMeta.id)} to wait without polling.`),
|
|
565
|
+
structuredContent: {
|
|
566
|
+
sessionId: sessionMeta.id,
|
|
567
|
+
status: started.status,
|
|
568
|
+
output: "",
|
|
569
|
+
models: summarizeModelRunsForConsult(started.models),
|
|
570
|
+
artifacts: summarizeArtifactsForConsult(started.artifacts),
|
|
571
|
+
images: summarizeImageArtifactsForConsult(started.artifacts),
|
|
572
|
+
detached: true,
|
|
573
|
+
},
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
catch (error) {
|
|
577
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
578
|
+
await sessionStore
|
|
579
|
+
.updateSession(sessionMeta.id, {
|
|
580
|
+
status: "error",
|
|
581
|
+
completedAt: new Date().toISOString(),
|
|
582
|
+
errorMessage: message,
|
|
583
|
+
response: { status: "error" },
|
|
584
|
+
error: { category: "internal", message },
|
|
585
|
+
})
|
|
586
|
+
.catch(() => undefined);
|
|
587
|
+
return {
|
|
588
|
+
isError: true,
|
|
589
|
+
content: textContent(`Unable to start detached session ${sessionMeta.id}: ${message}`),
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
}
|
|
521
593
|
const logWriter = sessionStore.createLogWriter(sessionMeta.id);
|
|
522
594
|
// Stream logs to both the session log and MCP logging notifications, but avoid buffering in memory
|
|
523
595
|
const log = (line) => {
|
|
@@ -585,9 +657,8 @@ export async function runConsultTool(input, { server }) {
|
|
|
585
657
|
export function registerConsultTool(server) {
|
|
586
658
|
server.registerTool("consult", {
|
|
587
659
|
title: "Run an oracle session",
|
|
588
|
-
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).',
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
}, async (input) => runConsultTool(input, { server: server.server }));
|
|
660
|
+
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; set `waitForCompletion:false` to return a durable sessionId immediately, then use `wait` to block without agent-side polling. 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).',
|
|
661
|
+
inputSchema: z.object(consultInputShape),
|
|
662
|
+
outputSchema: z.object(consultOutputShape),
|
|
663
|
+
}, async (input, context) => runConsultTool(input, { log: context.mcpReq.log }));
|
|
593
664
|
}
|
|
@@ -56,9 +56,9 @@ export function registerProjectSourcesTool(server) {
|
|
|
56
56
|
server.registerTool("project_sources", {
|
|
57
57
|
title: "Manage ChatGPT Project Sources",
|
|
58
58
|
description: "List or append files to a ChatGPT Project's persistent Sources tab. This is useful for Developer Mode workflows where chats do not share memory, but explicit project sources provide shared context. Destructive delete/replace/sync operations are intentionally not included in v1.",
|
|
59
|
-
inputSchema: projectSourcesInputShape,
|
|
60
|
-
outputSchema: projectSourcesOutputShape,
|
|
61
|
-
}, async (input) => {
|
|
59
|
+
inputSchema: z.object(projectSourcesInputShape),
|
|
60
|
+
outputSchema: z.object(projectSourcesOutputShape),
|
|
61
|
+
}, async (input, context) => {
|
|
62
62
|
const textContent = (text) => [{ type: "text", text }];
|
|
63
63
|
let parsed;
|
|
64
64
|
try {
|
|
@@ -107,9 +107,7 @@ export function registerProjectSourcesTool(server) {
|
|
|
107
107
|
dryRun: parsed.dryRun,
|
|
108
108
|
config: browserConfig,
|
|
109
109
|
log: (message) => {
|
|
110
|
-
|
|
111
|
-
.sendLoggingMessage({ level: "info", data: { text: message } })
|
|
112
|
-
.catch(() => undefined);
|
|
110
|
+
context.mcpReq.log("info", { text: message }).catch(() => undefined);
|
|
113
111
|
},
|
|
114
112
|
});
|
|
115
113
|
const output = result.status === "dry-run"
|
|
@@ -44,8 +44,8 @@ export function registerSessionsTool(server) {
|
|
|
44
44
|
server.registerTool("sessions", {
|
|
45
45
|
title: "List or fetch oracle sessions",
|
|
46
46
|
description: "Inspect Oracle session history stored under `ORACLE_HOME_DIR` (shared with the CLI). List recent sessions or fetch one by id/slug (optionally including metadata + request + log).",
|
|
47
|
-
inputSchema: sessionsInputShape,
|
|
48
|
-
outputSchema: sessionsOutputShape,
|
|
47
|
+
inputSchema: z.object(sessionsInputShape),
|
|
48
|
+
outputSchema: z.object(sessionsOutputShape),
|
|
49
49
|
}, async (input) => {
|
|
50
50
|
const textContent = (text) => [{ type: "text", text }];
|
|
51
51
|
const { id, hours = 24, limit = 100, includeAll = false, detail = false, } = sessionsInputSchema.parse(input);
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { realpathSync, watch } from "node:fs";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { sessionStore } from "../../sessionStore.js";
|
|
4
|
+
import { waitInputSchema } from "../types.js";
|
|
5
|
+
import { consultArtifactSummaryShape, consultImageSummaryShape, consultModelSummaryShape, readSessionLogTail, summarizeArtifactsForConsult, summarizeImageArtifactsForConsult, summarizeModelRunsForConsult, } from "./consult.js";
|
|
6
|
+
const TERMINAL_SESSION_STATUSES = new Set(["completed", "partial", "error", "cancelled"]);
|
|
7
|
+
const DEFAULT_FALLBACK_INTERVAL_MS = 1_000;
|
|
8
|
+
const waitInputShape = {
|
|
9
|
+
id: z.string().min(1, "Session id is required.").describe("Oracle session id or slug."),
|
|
10
|
+
timeoutMs: z
|
|
11
|
+
.number()
|
|
12
|
+
.int()
|
|
13
|
+
.nonnegative()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe("How long to wait for a terminal session state. Omit to wait indefinitely; 0 returns an immediate snapshot. A wait timeout never cancels the Oracle session."),
|
|
16
|
+
};
|
|
17
|
+
const waitOutputShape = {
|
|
18
|
+
sessionId: z.string(),
|
|
19
|
+
status: z.string(),
|
|
20
|
+
waitStatus: z.enum(["terminal", "timed_out"]),
|
|
21
|
+
timedOut: z.boolean(),
|
|
22
|
+
cancelled: z.boolean(),
|
|
23
|
+
output: z.string(),
|
|
24
|
+
models: z.array(consultModelSummaryShape).optional(),
|
|
25
|
+
artifacts: z.array(consultArtifactSummaryShape).optional(),
|
|
26
|
+
images: z.array(consultImageSummaryShape).optional(),
|
|
27
|
+
};
|
|
28
|
+
function abortReason(signal) {
|
|
29
|
+
return signal.reason instanceof Error
|
|
30
|
+
? signal.reason
|
|
31
|
+
: new DOMException("Oracle session wait was cancelled.", "AbortError");
|
|
32
|
+
}
|
|
33
|
+
function throwIfAborted(signal) {
|
|
34
|
+
if (signal?.aborted) {
|
|
35
|
+
throw abortReason(signal);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function isTerminalSessionStatus(status) {
|
|
39
|
+
return TERMINAL_SESSION_STATUSES.has(status);
|
|
40
|
+
}
|
|
41
|
+
export function createSessionChangeSource(directory) {
|
|
42
|
+
let watcher;
|
|
43
|
+
let notified = false;
|
|
44
|
+
let pendingWake;
|
|
45
|
+
const onWake = () => {
|
|
46
|
+
if (!pendingWake) {
|
|
47
|
+
notified = true;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const wake = pendingWake;
|
|
51
|
+
pendingWake = undefined;
|
|
52
|
+
wake();
|
|
53
|
+
};
|
|
54
|
+
const onError = () => {
|
|
55
|
+
const failedWatcher = watcher;
|
|
56
|
+
watcher = undefined;
|
|
57
|
+
failedWatcher?.off("change", onWake);
|
|
58
|
+
failedWatcher?.off("error", onError);
|
|
59
|
+
failedWatcher?.close();
|
|
60
|
+
onWake();
|
|
61
|
+
};
|
|
62
|
+
try {
|
|
63
|
+
// Windows short-path aliases can trigger a native libuv prefix assertion.
|
|
64
|
+
watcher = watch(realpathSync.native(directory));
|
|
65
|
+
watcher.on("change", onWake);
|
|
66
|
+
watcher.on("error", onError);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// A timer fallback below keeps waits correct when filesystem notifications
|
|
70
|
+
// are unavailable or the session directory is on an unsupported volume.
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
wait(delayMs, signal) {
|
|
74
|
+
throwIfAborted(signal);
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
let settled = false;
|
|
77
|
+
const finish = (error) => {
|
|
78
|
+
if (settled)
|
|
79
|
+
return;
|
|
80
|
+
settled = true;
|
|
81
|
+
clearTimeout(timer);
|
|
82
|
+
if (pendingWake === wake)
|
|
83
|
+
pendingWake = undefined;
|
|
84
|
+
signal?.removeEventListener("abort", onAbort);
|
|
85
|
+
if (error)
|
|
86
|
+
reject(error);
|
|
87
|
+
else
|
|
88
|
+
resolve();
|
|
89
|
+
};
|
|
90
|
+
const wake = () => finish();
|
|
91
|
+
const onAbort = () => finish(abortReason(signal));
|
|
92
|
+
const timer = setTimeout(wake, delayMs);
|
|
93
|
+
pendingWake = wake;
|
|
94
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
95
|
+
if (signal?.aborted) {
|
|
96
|
+
onAbort();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (notified) {
|
|
100
|
+
notified = false;
|
|
101
|
+
queueMicrotask(wake);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
close() {
|
|
106
|
+
watcher?.off("change", onWake);
|
|
107
|
+
watcher?.off("error", onError);
|
|
108
|
+
watcher?.close();
|
|
109
|
+
watcher = undefined;
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const defaultWaitDeps = {
|
|
114
|
+
readSession: (id) => sessionStore.readSession(id),
|
|
115
|
+
getSessionDir: async (id) => (await sessionStore.getPaths(id)).dir,
|
|
116
|
+
createChangeSource: createSessionChangeSource,
|
|
117
|
+
now: Date.now,
|
|
118
|
+
};
|
|
119
|
+
export async function waitForSessionTerminal({ id, timeoutMs, signal, fallbackIntervalMs = DEFAULT_FALLBACK_INTERVAL_MS, }, deps = defaultWaitDeps) {
|
|
120
|
+
throwIfAborted(signal);
|
|
121
|
+
const deadline = timeoutMs === undefined ? undefined : deps.now() + timeoutMs;
|
|
122
|
+
let metadata = await deps.readSession(id);
|
|
123
|
+
if (!metadata) {
|
|
124
|
+
throw new Error(`Session "${id}" not found.`);
|
|
125
|
+
}
|
|
126
|
+
if (isTerminalSessionStatus(metadata.status)) {
|
|
127
|
+
return { metadata, waitStatus: "terminal", timedOut: false };
|
|
128
|
+
}
|
|
129
|
+
if (timeoutMs === 0) {
|
|
130
|
+
return { metadata, waitStatus: "timed_out", timedOut: true };
|
|
131
|
+
}
|
|
132
|
+
const directory = await deps.getSessionDir(id);
|
|
133
|
+
const changes = deps.createChangeSource(directory);
|
|
134
|
+
try {
|
|
135
|
+
// Close the read/watch race: the session may have completed while the
|
|
136
|
+
// filesystem watcher was being installed.
|
|
137
|
+
metadata = (await deps.readSession(id)) ?? metadata;
|
|
138
|
+
while (!isTerminalSessionStatus(metadata.status)) {
|
|
139
|
+
throwIfAborted(signal);
|
|
140
|
+
const remaining = deadline === undefined ? undefined : deadline - deps.now();
|
|
141
|
+
if (remaining !== undefined && remaining <= 0) {
|
|
142
|
+
return { metadata, waitStatus: "timed_out", timedOut: true };
|
|
143
|
+
}
|
|
144
|
+
const delayMs = Math.max(1, Math.min(fallbackIntervalMs, remaining ?? fallbackIntervalMs));
|
|
145
|
+
await changes.wait(delayMs, signal);
|
|
146
|
+
metadata = (await deps.readSession(id)) ?? metadata;
|
|
147
|
+
}
|
|
148
|
+
return { metadata, waitStatus: "terminal", timedOut: false };
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
changes.close();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
export async function runWaitTool(input, extra) {
|
|
155
|
+
const { id, timeoutMs } = waitInputSchema.parse(input);
|
|
156
|
+
const result = await waitForSessionTerminal({ id, timeoutMs, signal: extra?.signal });
|
|
157
|
+
const { metadata } = result;
|
|
158
|
+
const logTail = (await readSessionLogTail(metadata.id, 4_000)) ?? "";
|
|
159
|
+
const summary = `Session ${metadata.id} (${metadata.status}; wait=${result.waitStatus})`;
|
|
160
|
+
return {
|
|
161
|
+
content: [{ type: "text", text: [summary, logTail || "(log empty)"].join("\n") }],
|
|
162
|
+
structuredContent: {
|
|
163
|
+
sessionId: metadata.id,
|
|
164
|
+
status: metadata.status,
|
|
165
|
+
waitStatus: result.waitStatus,
|
|
166
|
+
timedOut: result.timedOut,
|
|
167
|
+
cancelled: metadata.status === "cancelled",
|
|
168
|
+
output: logTail,
|
|
169
|
+
models: summarizeModelRunsForConsult(metadata.models),
|
|
170
|
+
artifacts: summarizeArtifactsForConsult(metadata.artifacts),
|
|
171
|
+
images: summarizeImageArtifactsForConsult(metadata.artifacts),
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
export function registerWaitTool(server) {
|
|
176
|
+
server.registerTool("wait", {
|
|
177
|
+
title: "Wait for an oracle session",
|
|
178
|
+
description: "Wait for an existing Oracle session to reach a terminal state without agent-side polling. Omit timeoutMs to wait indefinitely, or set a bounded caller wait. Wait timeout or request cancellation never cancels the Oracle session; call wait again with the same id to continue waiting.",
|
|
179
|
+
inputSchema: z.object(waitInputShape),
|
|
180
|
+
outputSchema: z.object(waitOutputShape),
|
|
181
|
+
}, async (input, context) => runWaitTool(input, { signal: context.mcpReq.signal }));
|
|
182
|
+
}
|
package/dist/src/mcp/types.js
CHANGED
|
@@ -29,6 +29,7 @@ export const consultInputSchema = z
|
|
|
29
29
|
browserKeepBrowser: z.boolean().optional(),
|
|
30
30
|
generateImage: z.string().optional(),
|
|
31
31
|
outputPath: z.string().optional(),
|
|
32
|
+
waitForCompletion: z.boolean().optional(),
|
|
32
33
|
dryRun: z.boolean().optional(),
|
|
33
34
|
search: z.boolean().optional(),
|
|
34
35
|
slug: z.string().optional(),
|
|
@@ -41,3 +42,9 @@ export const sessionsInputSchema = z.object({
|
|
|
41
42
|
includeAll: z.boolean().optional(),
|
|
42
43
|
detail: z.boolean().optional(),
|
|
43
44
|
});
|
|
45
|
+
export const waitInputSchema = z
|
|
46
|
+
.object({
|
|
47
|
+
id: z.string().min(1, "Session id is required."),
|
|
48
|
+
timeoutMs: z.number().int().nonnegative().optional(),
|
|
49
|
+
})
|
|
50
|
+
.strict();
|
|
@@ -26,6 +26,17 @@ export class BrowserAutomationError extends OracleUserError {
|
|
|
26
26
|
this.name = "BrowserAutomationError";
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* The caller went away. Distinct from a failure: nothing was wrong with the run,
|
|
31
|
+
* it simply stopped being wanted, and a caller that reads this should not retry
|
|
32
|
+
* or report a problem.
|
|
33
|
+
*/
|
|
34
|
+
export class BrowserRunCancelledError extends OracleUserError {
|
|
35
|
+
constructor(message = "Browser run cancelled: the caller disconnected.") {
|
|
36
|
+
super("browser-automation", message, { stage: "run-cancelled" });
|
|
37
|
+
this.name = "BrowserRunCancelledError";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
29
40
|
export class PromptValidationError extends OracleUserError {
|
|
30
41
|
constructor(message, details, cause) {
|
|
31
42
|
super("prompt-validation", message, details, cause);
|
|
@@ -75,7 +75,8 @@ function buildResolvedProviderRoute(input) {
|
|
|
75
75
|
});
|
|
76
76
|
const provider = state.provider;
|
|
77
77
|
const isAzureOpenAI = state.isAzureOpenAI;
|
|
78
|
-
|
|
78
|
+
// Azure owns the endpoint, including metadata lookups before request dispatch.
|
|
79
|
+
let baseUrl = isAzureOpenAI ? undefined : input.baseUrl?.trim();
|
|
79
80
|
const providerQualifiedOpenRouterCandidate = !isAzureOpenAI && providerMode !== "openai" && input.model.includes("/");
|
|
80
81
|
if (baseUrl &&
|
|
81
82
|
providerQualifiedOpenRouterCandidate &&
|
|
@@ -83,7 +84,7 @@ function buildResolvedProviderRoute(input) {
|
|
|
83
84
|
!isCustomBaseUrl(baseUrl)) {
|
|
84
85
|
baseUrl = undefined;
|
|
85
86
|
}
|
|
86
|
-
if (!baseUrl) {
|
|
87
|
+
if (!baseUrl && !isAzureOpenAI) {
|
|
87
88
|
let envBaseUrl;
|
|
88
89
|
if (input.model.startsWith("grok")) {
|
|
89
90
|
envBaseUrl = env.XAI_BASE_URL?.trim() || "https://api.x.ai/v1";
|