@indykish/oracle 0.9.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.
Files changed (131) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +215 -0
  3. package/assets-oracle-icon.png +0 -0
  4. package/dist/bin/oracle-cli.js +1252 -0
  5. package/dist/bin/oracle-mcp.js +6 -0
  6. package/dist/scripts/agent-send.js +147 -0
  7. package/dist/scripts/browser-tools.js +536 -0
  8. package/dist/scripts/check.js +21 -0
  9. package/dist/scripts/debug/extract-chatgpt-response.js +53 -0
  10. package/dist/scripts/docs-list.js +110 -0
  11. package/dist/scripts/git-policy.js +125 -0
  12. package/dist/scripts/run-cli.js +14 -0
  13. package/dist/scripts/runner.js +1378 -0
  14. package/dist/scripts/test-browser.js +103 -0
  15. package/dist/scripts/test-remote-chrome.js +68 -0
  16. package/dist/src/bridge/connection.js +103 -0
  17. package/dist/src/bridge/userConfigFile.js +28 -0
  18. package/dist/src/browser/actions/assistantResponse.js +1067 -0
  19. package/dist/src/browser/actions/attachmentDataTransfer.js +138 -0
  20. package/dist/src/browser/actions/attachments.js +1910 -0
  21. package/dist/src/browser/actions/domEvents.js +19 -0
  22. package/dist/src/browser/actions/modelSelection.js +485 -0
  23. package/dist/src/browser/actions/navigation.js +445 -0
  24. package/dist/src/browser/actions/promptComposer.js +485 -0
  25. package/dist/src/browser/actions/remoteFileTransfer.js +37 -0
  26. package/dist/src/browser/actions/thinkingTime.js +206 -0
  27. package/dist/src/browser/chromeLifecycle.js +344 -0
  28. package/dist/src/browser/config.js +103 -0
  29. package/dist/src/browser/constants.js +71 -0
  30. package/dist/src/browser/cookies.js +191 -0
  31. package/dist/src/browser/detect.js +164 -0
  32. package/dist/src/browser/domDebug.js +36 -0
  33. package/dist/src/browser/index.js +1741 -0
  34. package/dist/src/browser/modelStrategy.js +13 -0
  35. package/dist/src/browser/pageActions.js +5 -0
  36. package/dist/src/browser/policies.js +43 -0
  37. package/dist/src/browser/profileState.js +280 -0
  38. package/dist/src/browser/prompt.js +152 -0
  39. package/dist/src/browser/promptSummary.js +20 -0
  40. package/dist/src/browser/reattach.js +186 -0
  41. package/dist/src/browser/reattachHelpers.js +382 -0
  42. package/dist/src/browser/sessionRunner.js +119 -0
  43. package/dist/src/browser/types.js +1 -0
  44. package/dist/src/browser/utils.js +122 -0
  45. package/dist/src/browserMode.js +1 -0
  46. package/dist/src/cli/bridge/claudeConfig.js +54 -0
  47. package/dist/src/cli/bridge/client.js +73 -0
  48. package/dist/src/cli/bridge/codexConfig.js +43 -0
  49. package/dist/src/cli/bridge/doctor.js +107 -0
  50. package/dist/src/cli/bridge/host.js +259 -0
  51. package/dist/src/cli/browserConfig.js +278 -0
  52. package/dist/src/cli/browserDefaults.js +81 -0
  53. package/dist/src/cli/bundleWarnings.js +9 -0
  54. package/dist/src/cli/clipboard.js +10 -0
  55. package/dist/src/cli/detach.js +11 -0
  56. package/dist/src/cli/dryRun.js +105 -0
  57. package/dist/src/cli/duplicatePromptGuard.js +14 -0
  58. package/dist/src/cli/engine.js +41 -0
  59. package/dist/src/cli/errorUtils.js +9 -0
  60. package/dist/src/cli/format.js +13 -0
  61. package/dist/src/cli/help.js +77 -0
  62. package/dist/src/cli/hiddenAliases.js +22 -0
  63. package/dist/src/cli/markdownBundle.js +17 -0
  64. package/dist/src/cli/markdownRenderer.js +97 -0
  65. package/dist/src/cli/notifier.js +306 -0
  66. package/dist/src/cli/options.js +281 -0
  67. package/dist/src/cli/oscUtils.js +2 -0
  68. package/dist/src/cli/promptRequirement.js +17 -0
  69. package/dist/src/cli/renderFlags.js +9 -0
  70. package/dist/src/cli/renderOutput.js +26 -0
  71. package/dist/src/cli/rootAlias.js +30 -0
  72. package/dist/src/cli/runOptions.js +78 -0
  73. package/dist/src/cli/sessionCommand.js +111 -0
  74. package/dist/src/cli/sessionDisplay.js +567 -0
  75. package/dist/src/cli/sessionRunner.js +602 -0
  76. package/dist/src/cli/sessionTable.js +92 -0
  77. package/dist/src/cli/tagline.js +258 -0
  78. package/dist/src/cli/tui/index.js +486 -0
  79. package/dist/src/cli/writeOutputPath.js +21 -0
  80. package/dist/src/config.js +26 -0
  81. package/dist/src/gemini-web/client.js +328 -0
  82. package/dist/src/gemini-web/executor.js +285 -0
  83. package/dist/src/gemini-web/index.js +1 -0
  84. package/dist/src/gemini-web/types.js +1 -0
  85. package/dist/src/heartbeat.js +43 -0
  86. package/dist/src/mcp/server.js +40 -0
  87. package/dist/src/mcp/tools/consult.js +290 -0
  88. package/dist/src/mcp/tools/sessionResources.js +75 -0
  89. package/dist/src/mcp/tools/sessions.js +105 -0
  90. package/dist/src/mcp/types.js +22 -0
  91. package/dist/src/mcp/utils.js +37 -0
  92. package/dist/src/oracle/background.js +141 -0
  93. package/dist/src/oracle/claude.js +101 -0
  94. package/dist/src/oracle/client.js +197 -0
  95. package/dist/src/oracle/config.js +227 -0
  96. package/dist/src/oracle/errors.js +132 -0
  97. package/dist/src/oracle/files.js +378 -0
  98. package/dist/src/oracle/finishLine.js +32 -0
  99. package/dist/src/oracle/format.js +30 -0
  100. package/dist/src/oracle/fsAdapter.js +10 -0
  101. package/dist/src/oracle/gemini.js +195 -0
  102. package/dist/src/oracle/logging.js +36 -0
  103. package/dist/src/oracle/markdown.js +46 -0
  104. package/dist/src/oracle/modelResolver.js +183 -0
  105. package/dist/src/oracle/multiModelRunner.js +153 -0
  106. package/dist/src/oracle/oscProgress.js +24 -0
  107. package/dist/src/oracle/promptAssembly.js +13 -0
  108. package/dist/src/oracle/request.js +50 -0
  109. package/dist/src/oracle/run.js +596 -0
  110. package/dist/src/oracle/runUtils.js +31 -0
  111. package/dist/src/oracle/tokenEstimate.js +37 -0
  112. package/dist/src/oracle/tokenStats.js +39 -0
  113. package/dist/src/oracle/tokenStringifier.js +24 -0
  114. package/dist/src/oracle/types.js +1 -0
  115. package/dist/src/oracle.js +12 -0
  116. package/dist/src/oracleHome.js +13 -0
  117. package/dist/src/remote/client.js +129 -0
  118. package/dist/src/remote/health.js +113 -0
  119. package/dist/src/remote/remoteServiceConfig.js +31 -0
  120. package/dist/src/remote/server.js +533 -0
  121. package/dist/src/remote/types.js +1 -0
  122. package/dist/src/sessionManager.js +637 -0
  123. package/dist/src/sessionStore.js +56 -0
  124. package/dist/src/version.js +39 -0
  125. package/dist/vendor/oracle-notifier/OracleNotifier.swift +45 -0
  126. package/dist/vendor/oracle-notifier/README.md +24 -0
  127. package/dist/vendor/oracle-notifier/build-notifier.sh +93 -0
  128. package/package.json +115 -0
  129. package/vendor/oracle-notifier/OracleNotifier.swift +45 -0
  130. package/vendor/oracle-notifier/README.md +24 -0
  131. package/vendor/oracle-notifier/build-notifier.sh +93 -0
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ import 'dotenv/config';
3
+ import process from 'node:process';
4
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
5
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
6
+ import { getCliVersion } from '../version.js';
7
+ import { registerConsultTool } from './tools/consult.js';
8
+ import { registerSessionsTool } from './tools/sessions.js';
9
+ import { registerSessionResources } from './tools/sessionResources.js';
10
+ export async function startMcpServer() {
11
+ const server = new McpServer({
12
+ name: 'oracle-mcp',
13
+ version: getCliVersion(),
14
+ }, {
15
+ capabilities: {
16
+ logging: {},
17
+ },
18
+ });
19
+ registerConsultTool(server);
20
+ registerSessionsTool(server);
21
+ registerSessionResources(server);
22
+ const transport = new StdioServerTransport();
23
+ transport.onerror = (error) => {
24
+ console.error('MCP transport error:', error);
25
+ };
26
+ const closed = new Promise((resolve) => {
27
+ transport.onclose = () => {
28
+ resolve();
29
+ };
30
+ });
31
+ // Keep the process alive until the client closes the transport.
32
+ await server.connect(transport);
33
+ await closed;
34
+ }
35
+ if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith('oracle-mcp')) {
36
+ startMcpServer().catch((error) => {
37
+ console.error('Failed to start oracle-mcp:', error);
38
+ process.exitCode = 1;
39
+ });
40
+ }
@@ -0,0 +1,290 @@
1
+ import { z } from 'zod';
2
+ import { getCliVersion } from '../../version.js';
3
+ import { LoggingMessageNotificationParamsSchema } from '@modelcontextprotocol/sdk/types.js';
4
+ import { ensureBrowserAvailable, mapConsultToRunOptions } from '../utils.js';
5
+ import { sessionStore } from '../../sessionStore.js';
6
+ import { resolveRemoteServiceConfig } from '../../remote/remoteServiceConfig.js';
7
+ import { createRemoteBrowserExecutor } from '../../remote/client.js';
8
+ async function readSessionLogTail(sessionId, maxBytes) {
9
+ try {
10
+ const log = await sessionStore.readLog(sessionId);
11
+ if (log.length <= maxBytes) {
12
+ return log;
13
+ }
14
+ return log.slice(-maxBytes);
15
+ }
16
+ catch {
17
+ return null;
18
+ }
19
+ }
20
+ import { performSessionRun } from '../../cli/sessionRunner.js';
21
+ import { CHATGPT_URL } from '../../browser/constants.js';
22
+ import { consultInputSchema } from '../types.js';
23
+ import { loadUserConfig } from '../../config.js';
24
+ import { resolveNotificationSettings } from '../../cli/notifier.js';
25
+ import { mapModelToBrowserLabel, resolveBrowserModelLabel } from '../../cli/browserConfig.js';
26
+ // Use raw shapes so the MCP SDK (with its bundled Zod) wraps them and emits valid JSON Schema.
27
+ const consultInputShape = {
28
+ prompt: z
29
+ .string()
30
+ .min(1, 'Prompt is required.')
31
+ .describe('User prompt to run.'),
32
+ files: z
33
+ .array(z.string())
34
+ .default([])
35
+ .describe('Optional file paths or glob patterns (like the CLI `--file`). Resolved relative to the MCP server working directory.'),
36
+ model: z
37
+ .string()
38
+ .optional()
39
+ .describe('Single model name/label. Prefer setting `engine` explicitly to avoid default surprises.'),
40
+ models: z
41
+ .array(z.string())
42
+ .optional()
43
+ .describe('Multi-model fan-out (API engine only). Cannot be combined with browser automation.'),
44
+ engine: z
45
+ .enum(['api', 'browser'])
46
+ .optional()
47
+ .describe('Execution engine. `api` uses OpenAI/other providers. `browser` automates the ChatGPT web UI (supports attachments and ChatGPT-only model labels).'),
48
+ browserModelLabel: z
49
+ .string()
50
+ .optional()
51
+ .describe('Browser-only: explicit ChatGPT UI label to select (overrides model mapping). Example: "GPT-5.2 Thinking".'),
52
+ browserAttachments: z
53
+ .enum(['auto', 'never', 'always'])
54
+ .optional()
55
+ .describe('Browser-only: how to deliver `files`. Use "always" for real ChatGPT file uploads (including images/PDFs). Use "never" to paste file contents inline. "auto" chooses based on prompt size.'),
56
+ browserBundleFiles: z
57
+ .boolean()
58
+ .optional()
59
+ .describe('Browser-only: bundle many files into a single upload (helps with upload limits).'),
60
+ browserThinkingTime: z
61
+ .enum(['light', 'standard', 'extended', 'heavy'])
62
+ .optional()
63
+ .describe('Browser-only: set ChatGPT thinking time when supported by the chosen model.'),
64
+ browserKeepBrowser: z
65
+ .boolean()
66
+ .optional()
67
+ .describe('Browser-only: keep Chrome running after completion (useful for debugging).'),
68
+ search: z
69
+ .boolean()
70
+ .optional()
71
+ .describe('API-only: enable/disable the provider search tool (browser engine ignores this).'),
72
+ slug: z
73
+ .string()
74
+ .optional()
75
+ .describe('Optional human-friendly session id (used for later `oracle sessions` lookups).'),
76
+ };
77
+ const consultModelSummaryShape = z.object({
78
+ model: z.string(),
79
+ status: z.string(),
80
+ startedAt: z.string().optional(),
81
+ completedAt: z.string().optional(),
82
+ usage: z
83
+ .object({
84
+ inputTokens: z.number().optional(),
85
+ outputTokens: z.number().optional(),
86
+ reasoningTokens: z.number().optional(),
87
+ totalTokens: z.number().optional(),
88
+ cost: z.number().optional(),
89
+ })
90
+ .optional(),
91
+ response: z
92
+ .object({
93
+ id: z.string().optional(),
94
+ requestId: z.string().optional(),
95
+ status: z.string().optional(),
96
+ })
97
+ .optional(),
98
+ error: z
99
+ .object({
100
+ category: z.string().optional(),
101
+ message: z.string().optional(),
102
+ })
103
+ .optional(),
104
+ logPath: z.string().optional(),
105
+ });
106
+ const consultOutputShape = {
107
+ sessionId: z.string(),
108
+ status: z.string(),
109
+ output: z.string(),
110
+ models: z.array(consultModelSummaryShape).optional(),
111
+ };
112
+ export function summarizeModelRunsForConsult(runs) {
113
+ if (!runs || runs.length === 0) {
114
+ return undefined;
115
+ }
116
+ return runs.map((run) => {
117
+ const response = run.response
118
+ ? {
119
+ id: run.response.id ?? undefined,
120
+ requestId: run.response.requestId ?? undefined,
121
+ status: run.response.status ?? undefined,
122
+ }
123
+ : undefined;
124
+ const error = run.error
125
+ ? {
126
+ category: run.error.category,
127
+ message: run.error.message,
128
+ }
129
+ : undefined;
130
+ return {
131
+ model: run.model,
132
+ status: run.status ?? 'unknown',
133
+ startedAt: run.startedAt,
134
+ completedAt: run.completedAt,
135
+ usage: run.usage,
136
+ response,
137
+ error,
138
+ logPath: run.log?.path,
139
+ };
140
+ });
141
+ }
142
+ export function registerConsultTool(server) {
143
+ server.registerTool('consult', {
144
+ title: 'Run an oracle session',
145
+ description: 'Run a one-shot Oracle session (API or ChatGPT browser automation). Use `files` to attach project context. For browser-based image/file uploads, set `browserAttachments:"always"`. Sessions are stored under `ORACLE_HOME_DIR` (shared with the CLI).',
146
+ // Cast to any to satisfy SDK typings across differing Zod versions.
147
+ inputSchema: consultInputShape,
148
+ outputSchema: consultOutputShape,
149
+ }, async (input) => {
150
+ const textContent = (text) => [{ type: 'text', text }];
151
+ const { prompt, files, model, models, engine, search, browserModelLabel, browserAttachments, browserBundleFiles, browserThinkingTime, browserKeepBrowser, slug, } = consultInputSchema.parse(input);
152
+ const { config: userConfig } = await loadUserConfig();
153
+ const { runOptions, resolvedEngine } = mapConsultToRunOptions({
154
+ prompt,
155
+ files: files ?? [],
156
+ model,
157
+ models,
158
+ engine,
159
+ search,
160
+ browserAttachments,
161
+ browserBundleFiles,
162
+ userConfig,
163
+ env: process.env,
164
+ });
165
+ const cwd = process.cwd();
166
+ const resolvedRemote = resolveRemoteServiceConfig({ userConfig, env: process.env });
167
+ const browserGuard = ensureBrowserAvailable(resolvedEngine, { remoteHost: resolvedRemote.host });
168
+ if (resolvedEngine === 'browser' && browserGuard) {
169
+ return {
170
+ isError: true,
171
+ content: textContent(browserGuard),
172
+ };
173
+ }
174
+ let browserDeps;
175
+ if (resolvedEngine === 'browser' && resolvedRemote.host) {
176
+ if (!resolvedRemote.token) {
177
+ return {
178
+ isError: true,
179
+ content: textContent(`Remote host configured (${resolvedRemote.host}) but remote token is missing. Run \`oracle bridge client --connect <...>\` or set ORACLE_REMOTE_TOKEN.`),
180
+ };
181
+ }
182
+ browserDeps = {
183
+ executeBrowser: createRemoteBrowserExecutor({ host: resolvedRemote.host, token: resolvedRemote.token }),
184
+ };
185
+ }
186
+ let browserConfig;
187
+ if (resolvedEngine === 'browser') {
188
+ const envProfileDir = (process.env.ORACLE_BROWSER_PROFILE_DIR ?? '').trim();
189
+ const hasProfileDir = envProfileDir.length > 0;
190
+ const preferredLabel = (browserModelLabel ?? model)?.trim();
191
+ const isChatGptModel = runOptions.model.startsWith('gpt-') && !runOptions.model.includes('codex');
192
+ const desiredModelLabel = isChatGptModel
193
+ ? mapModelToBrowserLabel(runOptions.model)
194
+ : resolveBrowserModelLabel(preferredLabel, runOptions.model);
195
+ const configuredUrl = userConfig.browser?.chatgptUrl ?? userConfig.browser?.url ?? undefined;
196
+ // Default to manual-login when a persistent profile dir is provided (common for Codex/Claude).
197
+ const manualLogin = hasProfileDir;
198
+ browserConfig = {
199
+ url: configuredUrl ?? CHATGPT_URL,
200
+ cookieSync: !manualLogin,
201
+ headless: false,
202
+ hideWindow: false,
203
+ keepBrowser: browserKeepBrowser ?? false,
204
+ manualLogin,
205
+ manualLoginProfileDir: manualLogin ? envProfileDir : null,
206
+ thinkingTime: browserThinkingTime,
207
+ desiredModel: desiredModelLabel || mapModelToBrowserLabel(runOptions.model),
208
+ };
209
+ }
210
+ const notifications = resolveNotificationSettings({
211
+ cliNotify: undefined,
212
+ cliNotifySound: undefined,
213
+ env: process.env,
214
+ config: userConfig.notify,
215
+ });
216
+ const sessionMeta = await sessionStore.createSession({
217
+ ...runOptions,
218
+ mode: resolvedEngine,
219
+ slug,
220
+ browserConfig,
221
+ waitPreference: true,
222
+ }, cwd, notifications);
223
+ const logWriter = sessionStore.createLogWriter(sessionMeta.id);
224
+ // Best-effort: emit MCP logging notifications for live chunks but never block the run.
225
+ const sendLog = (text, level = 'info') => server.server
226
+ .sendLoggingMessage(LoggingMessageNotificationParamsSchema.parse({
227
+ level,
228
+ data: { text, bytes: Buffer.byteLength(text, 'utf8') },
229
+ }))
230
+ .catch(() => { });
231
+ // Stream logs to both the session log and MCP logging notifications, but avoid buffering in memory
232
+ const log = (line) => {
233
+ logWriter.logLine(line);
234
+ if (line !== undefined) {
235
+ sendLog(line);
236
+ }
237
+ };
238
+ const write = (chunk) => {
239
+ logWriter.writeChunk(chunk);
240
+ sendLog(chunk, 'debug');
241
+ return true;
242
+ };
243
+ try {
244
+ await performSessionRun({
245
+ sessionMeta,
246
+ runOptions,
247
+ mode: resolvedEngine,
248
+ browserConfig,
249
+ cwd,
250
+ log,
251
+ write,
252
+ version: getCliVersion(),
253
+ notifications,
254
+ muteStdout: true,
255
+ browserDeps,
256
+ });
257
+ }
258
+ catch (error) {
259
+ log(`Run failed: ${error instanceof Error ? error.message : String(error)}`);
260
+ return {
261
+ isError: true,
262
+ content: textContent(`Session ${sessionMeta.id} failed: ${error instanceof Error ? error.message : String(error)}`),
263
+ };
264
+ }
265
+ finally {
266
+ logWriter.stream.end();
267
+ }
268
+ try {
269
+ const finalMeta = (await sessionStore.readSession(sessionMeta.id)) ?? sessionMeta;
270
+ const summary = `Session ${sessionMeta.id} (${finalMeta.status})`;
271
+ const logTail = await readSessionLogTail(sessionMeta.id, 4000);
272
+ const modelsSummary = summarizeModelRunsForConsult(finalMeta.models);
273
+ return {
274
+ content: textContent([summary, logTail || '(log empty)'].join('\n').trim()),
275
+ structuredContent: {
276
+ sessionId: sessionMeta.id,
277
+ status: finalMeta.status,
278
+ output: logTail ?? '',
279
+ models: modelsSummary,
280
+ },
281
+ };
282
+ }
283
+ catch (error) {
284
+ return {
285
+ isError: true,
286
+ content: textContent(`Session completed but metadata fetch failed: ${error instanceof Error ? error.message : String(error)}`),
287
+ };
288
+ }
289
+ });
290
+ }
@@ -0,0 +1,75 @@
1
+ import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import fs from 'node:fs/promises';
3
+ import { sessionStore } from '../../sessionStore.js';
4
+ // URIs:
5
+ // - oracle-session://<id>/metadata
6
+ // - oracle-session://<id>/log
7
+ // - oracle-session://<id>/request
8
+ export function registerSessionResources(server) {
9
+ const template = new ResourceTemplate('oracle-session://{id}/{kind}', { list: undefined });
10
+ server.registerResource('oracle-session', template, {
11
+ title: 'oracle session resources',
12
+ description: 'Read stored session metadata, log, or request payload.',
13
+ }, async (uri, variables) => {
14
+ const idRaw = variables?.id;
15
+ const kindRaw = variables?.kind;
16
+ // uri-template variables arrive as string | string[]; collapse to first value.
17
+ const id = Array.isArray(idRaw) ? idRaw[0] : idRaw;
18
+ const kind = Array.isArray(kindRaw) ? kindRaw[0] : kindRaw;
19
+ if (!id || !kind) {
20
+ throw new Error('Missing id or kind');
21
+ }
22
+ switch (kind) {
23
+ case 'metadata': {
24
+ const metadata = await sessionStore.readSession(id);
25
+ if (!metadata) {
26
+ throw new Error(`Session "${id}" not found.`);
27
+ }
28
+ return {
29
+ contents: [
30
+ {
31
+ uri: uri.href,
32
+ text: JSON.stringify(metadata, null, 2),
33
+ },
34
+ ],
35
+ };
36
+ }
37
+ case 'log': {
38
+ const log = await sessionStore.readLog(id);
39
+ return {
40
+ contents: [
41
+ {
42
+ uri: uri.href,
43
+ text: log,
44
+ },
45
+ ],
46
+ };
47
+ }
48
+ case 'request': {
49
+ const request = await sessionStore.readRequest(id);
50
+ if (request) {
51
+ return {
52
+ contents: [
53
+ {
54
+ uri: uri.href,
55
+ text: JSON.stringify(request, null, 2),
56
+ },
57
+ ],
58
+ };
59
+ }
60
+ const paths = await sessionStore.getPaths(id);
61
+ const raw = await fs.readFile(paths.request, 'utf8');
62
+ return {
63
+ contents: [
64
+ {
65
+ uri: uri.href,
66
+ text: raw,
67
+ },
68
+ ],
69
+ };
70
+ }
71
+ default:
72
+ throw new Error(`Unsupported resource kind: ${kind}`);
73
+ }
74
+ });
75
+ }
@@ -0,0 +1,105 @@
1
+ import { z } from 'zod';
2
+ import { sessionStore } from '../../sessionStore.js';
3
+ import { sessionsInputSchema } from '../types.js';
4
+ const sessionsInputShape = {
5
+ id: z
6
+ .string()
7
+ .optional()
8
+ .describe('Session id or slug. If set, returns a single session (use detail:true to include metadata/request).'),
9
+ hours: z.number().optional().describe('Look back this many hours when listing sessions (default: 24).'),
10
+ limit: z.number().optional().describe('Maximum sessions to return when listing (default: 100).'),
11
+ includeAll: z
12
+ .boolean()
13
+ .optional()
14
+ .describe('Include sessions outside the time window when listing (mirrors `oracle status --all`).'),
15
+ detail: z
16
+ .boolean()
17
+ .optional()
18
+ .describe('When id is set, include session metadata + stored request + full log text.'),
19
+ };
20
+ const sessionsOutputShape = {
21
+ entries: z
22
+ .array(z.object({
23
+ id: z.string(),
24
+ createdAt: z.string(),
25
+ status: z.string(),
26
+ model: z.string().optional(),
27
+ mode: z.string().optional(),
28
+ }))
29
+ .optional(),
30
+ total: z.number().optional(),
31
+ truncated: z.boolean().optional(),
32
+ session: z
33
+ .object({
34
+ metadata: z.record(z.string(), z.any()),
35
+ log: z.string(),
36
+ request: z.record(z.string(), z.any()).optional(),
37
+ })
38
+ .optional(),
39
+ };
40
+ export function registerSessionsTool(server) {
41
+ server.registerTool('sessions', {
42
+ title: 'List or fetch oracle sessions',
43
+ 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).',
44
+ inputSchema: sessionsInputShape,
45
+ outputSchema: sessionsOutputShape,
46
+ }, async (input) => {
47
+ const textContent = (text) => [{ type: 'text', text }];
48
+ const { id, hours = 24, limit = 100, includeAll = false, detail = false } = sessionsInputSchema.parse(input);
49
+ if (id) {
50
+ if (!detail) {
51
+ const metadata = await sessionStore.readSession(id);
52
+ if (!metadata) {
53
+ throw new Error(`Session "${id}" not found.`);
54
+ }
55
+ return {
56
+ content: textContent(`${metadata.createdAt} | ${metadata.status} | ${metadata.model ?? 'n/a'} | ${metadata.id}`),
57
+ structuredContent: {
58
+ entries: [
59
+ {
60
+ id: metadata.id,
61
+ createdAt: metadata.createdAt,
62
+ status: metadata.status,
63
+ model: metadata.model,
64
+ mode: metadata.mode,
65
+ },
66
+ ],
67
+ total: 1,
68
+ truncated: false,
69
+ },
70
+ };
71
+ }
72
+ const metadata = await sessionStore.readSession(id);
73
+ if (!metadata) {
74
+ throw new Error(`Session "${id}" not found.`);
75
+ }
76
+ const log = await sessionStore.readLog(id);
77
+ const request = (await sessionStore.readRequest(id)) ?? undefined;
78
+ return {
79
+ content: textContent(log),
80
+ structuredContent: { session: { metadata, log, request } },
81
+ };
82
+ }
83
+ const metas = await sessionStore.listSessions();
84
+ const { entries, truncated, total } = sessionStore.filterSessions(metas, { hours, includeAll, limit });
85
+ return {
86
+ content: [
87
+ {
88
+ type: 'text',
89
+ text: entries.map((entry) => `${entry.createdAt} | ${entry.status} | ${entry.model ?? 'n/a'} | ${entry.id}`).join('\n'),
90
+ },
91
+ ],
92
+ structuredContent: {
93
+ entries: entries.map((entry) => ({
94
+ id: entry.id,
95
+ createdAt: entry.createdAt,
96
+ status: entry.status,
97
+ model: entry.model,
98
+ mode: entry.mode,
99
+ })),
100
+ total,
101
+ truncated,
102
+ },
103
+ };
104
+ });
105
+ }
@@ -0,0 +1,22 @@
1
+ import { z } from 'zod';
2
+ export const consultInputSchema = z.object({
3
+ prompt: z.string().min(1, 'Prompt is required.'),
4
+ files: z.array(z.string()).default([]),
5
+ model: z.string().optional(),
6
+ models: z.array(z.string()).optional(),
7
+ engine: z.enum(['api', 'browser']).optional(),
8
+ browserModelLabel: z.string().optional(),
9
+ browserAttachments: z.enum(['auto', 'never', 'always']).optional(),
10
+ browserBundleFiles: z.boolean().optional(),
11
+ browserThinkingTime: z.enum(['light', 'standard', 'extended', 'heavy']).optional(),
12
+ browserKeepBrowser: z.boolean().optional(),
13
+ search: z.boolean().optional(),
14
+ slug: z.string().optional(),
15
+ });
16
+ export const sessionsInputSchema = z.object({
17
+ id: z.string().optional(),
18
+ hours: z.number().optional(),
19
+ limit: z.number().optional(),
20
+ includeAll: z.boolean().optional(),
21
+ detail: z.boolean().optional(),
22
+ });
@@ -0,0 +1,37 @@
1
+ import { resolveRunOptionsFromConfig } from '../cli/runOptions.js';
2
+ import { Launcher } from 'chrome-launcher';
3
+ export function mapConsultToRunOptions({ prompt, files, model, models, engine, search, browserAttachments, browserBundleFiles, userConfig, env = process.env, }) {
4
+ // Normalize CLI-style inputs through the shared resolver so config/env defaults apply,
5
+ // then overlay MCP-only overrides such as explicit search toggles.
6
+ const mergedModels = Array.isArray(models) && models.length > 0
7
+ ? [model, ...models].filter((entry) => Boolean(entry?.trim()))
8
+ : models;
9
+ const result = resolveRunOptionsFromConfig({ prompt, files, model, models: mergedModels, engine, userConfig, env });
10
+ if (typeof search === 'boolean') {
11
+ result.runOptions.search = search;
12
+ }
13
+ if (browserAttachments) {
14
+ result.runOptions.browserAttachments = browserAttachments;
15
+ }
16
+ if (typeof browserBundleFiles === 'boolean') {
17
+ result.runOptions.browserBundleFiles = browserBundleFiles;
18
+ }
19
+ return result;
20
+ }
21
+ export function ensureBrowserAvailable(engine, options) {
22
+ if (engine !== 'browser') {
23
+ return null;
24
+ }
25
+ const remoteHost = options?.remoteHost?.trim() || process.env.ORACLE_REMOTE_HOST?.trim();
26
+ if (remoteHost) {
27
+ return null;
28
+ }
29
+ if (process.env.CHROME_PATH) {
30
+ return null;
31
+ }
32
+ const found = Launcher.getFirstInstallation();
33
+ if (!found) {
34
+ return 'Browser engine unavailable: no Chrome installation found and CHROME_PATH is unset.';
35
+ }
36
+ return null;
37
+ }