@steipete/oracle 0.8.6 → 0.10.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 (181) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +130 -45
  3. package/dist/bin/oracle-cli.js +613 -379
  4. package/dist/bin/oracle-mcp.js +2 -2
  5. package/dist/bin/oracle.js +165 -279
  6. package/dist/scripts/agent-send.js +31 -31
  7. package/dist/scripts/check.js +6 -6
  8. package/dist/scripts/debug/extract-chatgpt-response.js +10 -10
  9. package/dist/scripts/docs-list.js +30 -30
  10. package/dist/scripts/git-policy.js +25 -23
  11. package/dist/scripts/run-cli.js +8 -8
  12. package/dist/scripts/runner.js +203 -195
  13. package/dist/scripts/test-browser.js +21 -18
  14. package/dist/scripts/test-remote-chrome.js +20 -20
  15. package/dist/src/bridge/connection.js +18 -18
  16. package/dist/src/bridge/userConfigFile.js +7 -7
  17. package/dist/src/browser/actions/assistantResponse.js +149 -101
  18. package/dist/src/browser/actions/attachmentDataTransfer.js +49 -47
  19. package/dist/src/browser/actions/attachments.js +246 -150
  20. package/dist/src/browser/actions/domEvents.js +2 -2
  21. package/dist/src/browser/actions/modelSelection.js +314 -104
  22. package/dist/src/browser/actions/navigation.js +161 -136
  23. package/dist/src/browser/actions/promptComposer.js +100 -64
  24. package/dist/src/browser/actions/remoteFileTransfer.js +10 -10
  25. package/dist/src/browser/actions/thinkingTime.js +207 -110
  26. package/dist/src/browser/chromeLifecycle.js +62 -60
  27. package/dist/src/browser/config.js +34 -15
  28. package/dist/src/browser/constants.js +17 -12
  29. package/dist/src/browser/cookies.js +19 -19
  30. package/dist/src/browser/detect.js +62 -62
  31. package/dist/src/browser/domDebug.js +1 -1
  32. package/dist/src/browser/index.js +452 -303
  33. package/dist/src/browser/modelStrategy.js +1 -1
  34. package/dist/src/browser/pageActions.js +5 -5
  35. package/dist/src/browser/policies.js +16 -13
  36. package/dist/src/browser/profileState.js +44 -39
  37. package/dist/src/browser/prompt.js +72 -42
  38. package/dist/src/browser/promptSummary.js +5 -5
  39. package/dist/src/browser/providerDomFlow.js +17 -0
  40. package/dist/src/browser/providers/chatgptDomProvider.js +49 -0
  41. package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +254 -0
  42. package/dist/src/browser/providers/index.js +2 -0
  43. package/dist/src/browser/reattach.js +67 -34
  44. package/dist/src/browser/reattachHelpers.js +31 -26
  45. package/dist/src/browser/sessionRunner.js +37 -25
  46. package/dist/src/browser/utils.js +9 -9
  47. package/dist/src/browserMode.js +1 -1
  48. package/dist/src/cli/bridge/claudeConfig.js +16 -16
  49. package/dist/src/cli/bridge/client.js +28 -20
  50. package/dist/src/cli/bridge/codexConfig.js +16 -16
  51. package/dist/src/cli/bridge/doctor.js +47 -39
  52. package/dist/src/cli/bridge/host.js +58 -56
  53. package/dist/src/cli/browserConfig.js +65 -45
  54. package/dist/src/cli/browserDefaults.js +27 -26
  55. package/dist/src/cli/bundleWarnings.js +1 -1
  56. package/dist/src/cli/clipboard.js +11 -2
  57. package/dist/src/cli/detach.js +7 -4
  58. package/dist/src/cli/dryRun.js +29 -25
  59. package/dist/src/cli/duplicatePromptGuard.js +3 -3
  60. package/dist/src/cli/engine.js +9 -9
  61. package/dist/src/cli/errorUtils.js +1 -1
  62. package/dist/src/cli/fileSize.js +11 -0
  63. package/dist/src/cli/format.js +2 -2
  64. package/dist/src/cli/help.js +28 -28
  65. package/dist/src/cli/hiddenAliases.js +3 -3
  66. package/dist/src/cli/markdownBundle.js +12 -8
  67. package/dist/src/cli/markdownRenderer.js +15 -15
  68. package/dist/src/cli/notifier.js +77 -67
  69. package/dist/src/cli/options.js +145 -87
  70. package/dist/src/cli/oscUtils.js +1 -1
  71. package/dist/src/cli/promptRequirement.js +2 -2
  72. package/dist/src/cli/renderOutput.js +1 -1
  73. package/dist/src/cli/rootAlias.js +1 -1
  74. package/dist/src/cli/runOptions.js +37 -25
  75. package/dist/src/cli/sessionCommand.js +31 -21
  76. package/dist/src/cli/sessionDisplay.js +182 -79
  77. package/dist/src/cli/sessionLineage.js +60 -0
  78. package/dist/src/cli/sessionRunner.js +118 -90
  79. package/dist/src/cli/sessionTable.js +28 -24
  80. package/dist/src/cli/stdin.js +22 -0
  81. package/dist/src/cli/tagline.js +121 -124
  82. package/dist/src/cli/tui/index.js +140 -127
  83. package/dist/src/cli/writeOutputPath.js +5 -5
  84. package/dist/src/config.js +7 -7
  85. package/dist/src/gemini-web/browserSessionManager.js +80 -0
  86. package/dist/src/gemini-web/client.js +81 -64
  87. package/dist/src/gemini-web/executionMode.js +16 -0
  88. package/dist/src/gemini-web/executor.js +327 -169
  89. package/dist/src/gemini-web/index.js +1 -1
  90. package/dist/src/mcp/server.js +16 -12
  91. package/dist/src/mcp/tools/consult.js +81 -64
  92. package/dist/src/mcp/tools/sessionResources.js +12 -12
  93. package/dist/src/mcp/tools/sessions.js +26 -17
  94. package/dist/src/mcp/types.js +5 -5
  95. package/dist/src/mcp/utils.js +15 -7
  96. package/dist/src/oracle/background.js +15 -15
  97. package/dist/src/oracle/claude.js +53 -25
  98. package/dist/src/oracle/client.js +84 -46
  99. package/dist/src/oracle/config.js +124 -58
  100. package/dist/src/oracle/errors.js +38 -38
  101. package/dist/src/oracle/files.js +69 -45
  102. package/dist/src/oracle/finishLine.js +10 -8
  103. package/dist/src/oracle/format.js +3 -3
  104. package/dist/src/oracle/gemini.js +37 -30
  105. package/dist/src/oracle/logging.js +7 -7
  106. package/dist/src/oracle/markdown.js +28 -28
  107. package/dist/src/oracle/modelResolver.js +16 -16
  108. package/dist/src/oracle/multiModelRunner.js +12 -12
  109. package/dist/src/oracle/oscProgress.js +8 -8
  110. package/dist/src/oracle/promptAssembly.js +6 -3
  111. package/dist/src/oracle/request.js +23 -15
  112. package/dist/src/oracle/run.js +172 -140
  113. package/dist/src/oracle/runUtils.js +8 -5
  114. package/dist/src/oracle/tokenEstimate.js +6 -6
  115. package/dist/src/oracle/tokenStats.js +5 -5
  116. package/dist/src/oracle/tokenStringifier.js +5 -5
  117. package/dist/src/oracle.js +12 -12
  118. package/dist/src/oracleHome.js +3 -3
  119. package/dist/src/remote/client.js +25 -25
  120. package/dist/src/remote/health.js +20 -20
  121. package/dist/src/remote/remoteServiceConfig.js +9 -9
  122. package/dist/src/remote/server.js +129 -118
  123. package/dist/src/sessionManager.js +81 -75
  124. package/dist/src/sessionStore.js +3 -3
  125. package/dist/src/version.js +10 -10
  126. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  127. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  128. package/dist/vendor/oracle-notifier/README.md +2 -0
  129. package/package.json +69 -65
  130. package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  131. package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  132. package/vendor/oracle-notifier/README.md +2 -0
  133. package/dist/markdansi/types/index.js +0 -4
  134. package/dist/oracle/bin/oracle-cli.js +0 -472
  135. package/dist/oracle/src/browser/actions/assistantResponse.js +0 -471
  136. package/dist/oracle/src/browser/actions/attachments.js +0 -82
  137. package/dist/oracle/src/browser/actions/modelSelection.js +0 -190
  138. package/dist/oracle/src/browser/actions/navigation.js +0 -75
  139. package/dist/oracle/src/browser/actions/promptComposer.js +0 -167
  140. package/dist/oracle/src/browser/chromeLifecycle.js +0 -104
  141. package/dist/oracle/src/browser/config.js +0 -33
  142. package/dist/oracle/src/browser/constants.js +0 -40
  143. package/dist/oracle/src/browser/cookies.js +0 -210
  144. package/dist/oracle/src/browser/domDebug.js +0 -36
  145. package/dist/oracle/src/browser/index.js +0 -331
  146. package/dist/oracle/src/browser/pageActions.js +0 -5
  147. package/dist/oracle/src/browser/prompt.js +0 -88
  148. package/dist/oracle/src/browser/promptSummary.js +0 -20
  149. package/dist/oracle/src/browser/sessionRunner.js +0 -80
  150. package/dist/oracle/src/browser/utils.js +0 -62
  151. package/dist/oracle/src/browserMode.js +0 -1
  152. package/dist/oracle/src/cli/browserConfig.js +0 -44
  153. package/dist/oracle/src/cli/dryRun.js +0 -59
  154. package/dist/oracle/src/cli/engine.js +0 -17
  155. package/dist/oracle/src/cli/errorUtils.js +0 -9
  156. package/dist/oracle/src/cli/help.js +0 -70
  157. package/dist/oracle/src/cli/markdownRenderer.js +0 -15
  158. package/dist/oracle/src/cli/options.js +0 -103
  159. package/dist/oracle/src/cli/promptRequirement.js +0 -14
  160. package/dist/oracle/src/cli/rootAlias.js +0 -30
  161. package/dist/oracle/src/cli/sessionCommand.js +0 -77
  162. package/dist/oracle/src/cli/sessionDisplay.js +0 -270
  163. package/dist/oracle/src/cli/sessionRunner.js +0 -94
  164. package/dist/oracle/src/heartbeat.js +0 -43
  165. package/dist/oracle/src/oracle/client.js +0 -48
  166. package/dist/oracle/src/oracle/config.js +0 -29
  167. package/dist/oracle/src/oracle/errors.js +0 -101
  168. package/dist/oracle/src/oracle/files.js +0 -220
  169. package/dist/oracle/src/oracle/format.js +0 -33
  170. package/dist/oracle/src/oracle/fsAdapter.js +0 -7
  171. package/dist/oracle/src/oracle/oscProgress.js +0 -60
  172. package/dist/oracle/src/oracle/request.js +0 -48
  173. package/dist/oracle/src/oracle/run.js +0 -444
  174. package/dist/oracle/src/oracle/tokenStats.js +0 -39
  175. package/dist/oracle/src/oracle/types.js +0 -1
  176. package/dist/oracle/src/oracle.js +0 -9
  177. package/dist/oracle/src/sessionManager.js +0 -205
  178. package/dist/oracle/src/version.js +0 -39
  179. package/dist/scripts/chrome/browser-tools.js +0 -295
  180. package/dist/src/browser/profileSync.js +0 -141
  181. /package/dist/{oracle/src/browser/types.js → src/gemini-web/executionClients.js} +0 -0
@@ -1,10 +1,10 @@
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';
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
8
  async function readSessionLogTail(sessionId, maxBytes) {
9
9
  try {
10
10
  const log = await sessionStore.readLog(sessionId);
@@ -17,62 +17,59 @@ async function readSessionLogTail(sessionId, maxBytes) {
17
17
  return null;
18
18
  }
19
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';
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
26
  // Use raw shapes so the MCP SDK (with its bundled Zod) wraps them and emits valid JSON Schema.
27
27
  const consultInputShape = {
28
- prompt: z
29
- .string()
30
- .min(1, 'Prompt is required.')
31
- .describe('User prompt to run.'),
28
+ prompt: z.string().min(1, "Prompt is required.").describe("User prompt to run."),
32
29
  files: z
33
30
  .array(z.string())
34
31
  .default([])
35
- .describe('Optional file paths or glob patterns (like the CLI `--file`). Resolved relative to the MCP server working directory.'),
32
+ .describe("Optional file paths or glob patterns (like the CLI `--file`). Resolved relative to the MCP server working directory."),
36
33
  model: z
37
34
  .string()
38
35
  .optional()
39
- .describe('Single model name/label. Prefer setting `engine` explicitly to avoid default surprises.'),
36
+ .describe("Single model name/label. Prefer setting `engine` explicitly to avoid default surprises."),
40
37
  models: z
41
38
  .array(z.string())
42
39
  .optional()
43
- .describe('Multi-model fan-out (API engine only). Cannot be combined with browser automation.'),
40
+ .describe("Multi-model fan-out (API engine only). Cannot be combined with browser automation."),
44
41
  engine: z
45
- .enum(['api', 'browser'])
42
+ .enum(["api", "browser"])
46
43
  .optional()
47
- .describe('Execution engine. `api` uses OpenAI/other providers. `browser` automates the ChatGPT web UI (supports attachments and ChatGPT-only model labels).'),
44
+ .describe("Execution engine. `api` uses OpenAI/other providers. `browser` automates the ChatGPT web UI (supports attachments and ChatGPT-only model labels)."),
48
45
  browserModelLabel: z
49
46
  .string()
50
47
  .optional()
51
48
  .describe('Browser-only: explicit ChatGPT UI label to select (overrides model mapping). Example: "GPT-5.2 Thinking".'),
52
49
  browserAttachments: z
53
- .enum(['auto', 'never', 'always'])
50
+ .enum(["auto", "never", "always"])
54
51
  .optional()
55
52
  .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
53
  browserBundleFiles: z
57
54
  .boolean()
58
55
  .optional()
59
- .describe('Browser-only: bundle many files into a single upload (helps with upload limits).'),
56
+ .describe("Browser-only: bundle many files into a single upload (helps with upload limits)."),
60
57
  browserThinkingTime: z
61
- .enum(['light', 'standard', 'extended', 'heavy'])
58
+ .enum(["light", "standard", "extended", "heavy"])
62
59
  .optional()
63
- .describe('Browser-only: set ChatGPT thinking time when supported by the chosen model.'),
60
+ .describe("Browser-only: set ChatGPT thinking time when supported by the chosen model."),
64
61
  browserKeepBrowser: z
65
62
  .boolean()
66
63
  .optional()
67
- .describe('Browser-only: keep Chrome running after completion (useful for debugging).'),
64
+ .describe("Browser-only: keep Chrome running after completion (useful for debugging)."),
68
65
  search: z
69
66
  .boolean()
70
67
  .optional()
71
- .describe('API-only: enable/disable the provider search tool (browser engine ignores this).'),
68
+ .describe("API-only: enable/disable the provider search tool (browser engine ignores this)."),
72
69
  slug: z
73
70
  .string()
74
71
  .optional()
75
- .describe('Optional human-friendly session id (used for later `oracle sessions` lookups).'),
72
+ .describe("Optional human-friendly session id (used for later `oracle sessions` lookups)."),
76
73
  };
77
74
  const consultModelSummaryShape = z.object({
78
75
  model: z.string(),
@@ -129,7 +126,7 @@ export function summarizeModelRunsForConsult(runs) {
129
126
  : undefined;
130
127
  return {
131
128
  model: run.model,
132
- status: run.status ?? 'unknown',
129
+ status: run.status ?? "unknown",
133
130
  startedAt: run.startedAt,
134
131
  completedAt: run.completedAt,
135
132
  usage: run.usage,
@@ -139,15 +136,42 @@ export function summarizeModelRunsForConsult(runs) {
139
136
  };
140
137
  });
141
138
  }
139
+ export function buildConsultBrowserConfig({ userConfig, env, runModel, inputModel, browserModelLabel, browserThinkingTime, browserKeepBrowser, }) {
140
+ const configuredBrowser = userConfig.browser ?? {};
141
+ const envProfileDir = (env.ORACLE_BROWSER_PROFILE_DIR ?? "").trim();
142
+ const hasProfileDir = envProfileDir.length > 0;
143
+ const preferredLabel = (browserModelLabel ?? inputModel)?.trim();
144
+ const isChatGptModel = runModel.startsWith("gpt-") && !runModel.includes("codex");
145
+ const desiredModelLabel = isChatGptModel
146
+ ? mapModelToBrowserLabel(runModel)
147
+ : resolveBrowserModelLabel(preferredLabel, runModel);
148
+ const configuredUrl = configuredBrowser.chatgptUrl ?? configuredBrowser.url ?? CHATGPT_URL;
149
+ const manualLogin = hasProfileDir ? true : (configuredBrowser.manualLogin ?? false);
150
+ return {
151
+ ...configuredBrowser,
152
+ url: configuredUrl,
153
+ chatgptUrl: configuredUrl,
154
+ cookieSync: !manualLogin,
155
+ headless: configuredBrowser.headless ?? false,
156
+ hideWindow: configuredBrowser.hideWindow ?? false,
157
+ keepBrowser: browserKeepBrowser ?? configuredBrowser.keepBrowser ?? false,
158
+ manualLogin,
159
+ manualLoginProfileDir: manualLogin
160
+ ? ((envProfileDir || configuredBrowser.manualLoginProfileDir) ?? null)
161
+ : null,
162
+ thinkingTime: browserThinkingTime ?? configuredBrowser.thinkingTime,
163
+ desiredModel: desiredModelLabel || mapModelToBrowserLabel(runModel),
164
+ };
165
+ }
142
166
  export function registerConsultTool(server) {
143
- server.registerTool('consult', {
144
- title: 'Run an oracle session',
167
+ server.registerTool("consult", {
168
+ title: "Run an oracle session",
145
169
  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
170
  // Cast to any to satisfy SDK typings across differing Zod versions.
147
171
  inputSchema: consultInputShape,
148
172
  outputSchema: consultOutputShape,
149
173
  }, async (input) => {
150
- const textContent = (text) => [{ type: 'text', text }];
174
+ const textContent = (text) => [{ type: "text", text }];
151
175
  const { prompt, files, model, models, engine, search, browserModelLabel, browserAttachments, browserBundleFiles, browserThinkingTime, browserKeepBrowser, slug, } = consultInputSchema.parse(input);
152
176
  const { config: userConfig } = await loadUserConfig();
153
177
  const { runOptions, resolvedEngine } = mapConsultToRunOptions({
@@ -164,15 +188,17 @@ export function registerConsultTool(server) {
164
188
  });
165
189
  const cwd = process.cwd();
166
190
  const resolvedRemote = resolveRemoteServiceConfig({ userConfig, env: process.env });
167
- const browserGuard = ensureBrowserAvailable(resolvedEngine, { remoteHost: resolvedRemote.host });
168
- if (resolvedEngine === 'browser' && browserGuard) {
191
+ const browserGuard = ensureBrowserAvailable(resolvedEngine, {
192
+ remoteHost: resolvedRemote.host,
193
+ });
194
+ if (resolvedEngine === "browser" && browserGuard) {
169
195
  return {
170
196
  isError: true,
171
197
  content: textContent(browserGuard),
172
198
  };
173
199
  }
174
200
  let browserDeps;
175
- if (resolvedEngine === 'browser' && resolvedRemote.host) {
201
+ if (resolvedEngine === "browser" && resolvedRemote.host) {
176
202
  if (!resolvedRemote.token) {
177
203
  return {
178
204
  isError: true,
@@ -180,32 +206,23 @@ export function registerConsultTool(server) {
180
206
  };
181
207
  }
182
208
  browserDeps = {
183
- executeBrowser: createRemoteBrowserExecutor({ host: resolvedRemote.host, token: resolvedRemote.token }),
209
+ executeBrowser: createRemoteBrowserExecutor({
210
+ host: resolvedRemote.host,
211
+ token: resolvedRemote.token,
212
+ }),
184
213
  };
185
214
  }
186
215
  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
- };
216
+ if (resolvedEngine === "browser") {
217
+ browserConfig = buildConsultBrowserConfig({
218
+ userConfig,
219
+ env: process.env,
220
+ runModel: runOptions.model,
221
+ inputModel: model,
222
+ browserModelLabel,
223
+ browserThinkingTime,
224
+ browserKeepBrowser,
225
+ });
209
226
  }
210
227
  const notifications = resolveNotificationSettings({
211
228
  cliNotify: undefined,
@@ -222,10 +239,10 @@ export function registerConsultTool(server) {
222
239
  }, cwd, notifications);
223
240
  const logWriter = sessionStore.createLogWriter(sessionMeta.id);
224
241
  // Best-effort: emit MCP logging notifications for live chunks but never block the run.
225
- const sendLog = (text, level = 'info') => server.server
242
+ const sendLog = (text, level = "info") => server.server
226
243
  .sendLoggingMessage(LoggingMessageNotificationParamsSchema.parse({
227
244
  level,
228
- data: { text, bytes: Buffer.byteLength(text, 'utf8') },
245
+ data: { text, bytes: Buffer.byteLength(text, "utf8") },
229
246
  }))
230
247
  .catch(() => { });
231
248
  // Stream logs to both the session log and MCP logging notifications, but avoid buffering in memory
@@ -237,7 +254,7 @@ export function registerConsultTool(server) {
237
254
  };
238
255
  const write = (chunk) => {
239
256
  logWriter.writeChunk(chunk);
240
- sendLog(chunk, 'debug');
257
+ sendLog(chunk, "debug");
241
258
  return true;
242
259
  };
243
260
  try {
@@ -271,11 +288,11 @@ export function registerConsultTool(server) {
271
288
  const logTail = await readSessionLogTail(sessionMeta.id, 4000);
272
289
  const modelsSummary = summarizeModelRunsForConsult(finalMeta.models);
273
290
  return {
274
- content: textContent([summary, logTail || '(log empty)'].join('\n').trim()),
291
+ content: textContent([summary, logTail || "(log empty)"].join("\n").trim()),
275
292
  structuredContent: {
276
293
  sessionId: sessionMeta.id,
277
294
  status: finalMeta.status,
278
- output: logTail ?? '',
295
+ output: logTail ?? "",
279
296
  models: modelsSummary,
280
297
  },
281
298
  };
@@ -1,15 +1,15 @@
1
- import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import fs from 'node:fs/promises';
3
- import { sessionStore } from '../../sessionStore.js';
1
+ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import fs from "node:fs/promises";
3
+ import { sessionStore } from "../../sessionStore.js";
4
4
  // URIs:
5
5
  // - oracle-session://<id>/metadata
6
6
  // - oracle-session://<id>/log
7
7
  // - oracle-session://<id>/request
8
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.',
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
13
  }, async (uri, variables) => {
14
14
  const idRaw = variables?.id;
15
15
  const kindRaw = variables?.kind;
@@ -17,10 +17,10 @@ export function registerSessionResources(server) {
17
17
  const id = Array.isArray(idRaw) ? idRaw[0] : idRaw;
18
18
  const kind = Array.isArray(kindRaw) ? kindRaw[0] : kindRaw;
19
19
  if (!id || !kind) {
20
- throw new Error('Missing id or kind');
20
+ throw new Error("Missing id or kind");
21
21
  }
22
22
  switch (kind) {
23
- case 'metadata': {
23
+ case "metadata": {
24
24
  const metadata = await sessionStore.readSession(id);
25
25
  if (!metadata) {
26
26
  throw new Error(`Session "${id}" not found.`);
@@ -34,7 +34,7 @@ export function registerSessionResources(server) {
34
34
  ],
35
35
  };
36
36
  }
37
- case 'log': {
37
+ case "log": {
38
38
  const log = await sessionStore.readLog(id);
39
39
  return {
40
40
  contents: [
@@ -45,7 +45,7 @@ export function registerSessionResources(server) {
45
45
  ],
46
46
  };
47
47
  }
48
- case 'request': {
48
+ case "request": {
49
49
  const request = await sessionStore.readRequest(id);
50
50
  if (request) {
51
51
  return {
@@ -58,7 +58,7 @@ export function registerSessionResources(server) {
58
58
  };
59
59
  }
60
60
  const paths = await sessionStore.getPaths(id);
61
- const raw = await fs.readFile(paths.request, 'utf8');
61
+ const raw = await fs.readFile(paths.request, "utf8");
62
62
  return {
63
63
  contents: [
64
64
  {
@@ -1,21 +1,24 @@
1
- import { z } from 'zod';
2
- import { sessionStore } from '../../sessionStore.js';
3
- import { sessionsInputSchema } from '../types.js';
1
+ import { z } from "zod";
2
+ import { sessionStore } from "../../sessionStore.js";
3
+ import { sessionsInputSchema } from "../types.js";
4
4
  const sessionsInputShape = {
5
5
  id: z
6
6
  .string()
7
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).'),
8
+ .describe("Session id or slug. If set, returns a single session (use detail:true to include metadata/request)."),
9
+ hours: z
10
+ .number()
11
+ .optional()
12
+ .describe("Look back this many hours when listing sessions (default: 24)."),
13
+ limit: z.number().optional().describe("Maximum sessions to return when listing (default: 100)."),
11
14
  includeAll: z
12
15
  .boolean()
13
16
  .optional()
14
- .describe('Include sessions outside the time window when listing (mirrors `oracle status --all`).'),
17
+ .describe("Include sessions outside the time window when listing (mirrors `oracle status --all`)."),
15
18
  detail: z
16
19
  .boolean()
17
20
  .optional()
18
- .describe('When id is set, include session metadata + stored request + full log text.'),
21
+ .describe("When id is set, include session metadata + stored request + full log text."),
19
22
  };
20
23
  const sessionsOutputShape = {
21
24
  entries: z
@@ -38,14 +41,14 @@ const sessionsOutputShape = {
38
41
  .optional(),
39
42
  };
40
43
  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
+ server.registerTool("sessions", {
45
+ title: "List or fetch oracle sessions",
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).",
44
47
  inputSchema: sessionsInputShape,
45
48
  outputSchema: sessionsOutputShape,
46
49
  }, async (input) => {
47
- const textContent = (text) => [{ type: 'text', text }];
48
- const { id, hours = 24, limit = 100, includeAll = false, detail = false } = sessionsInputSchema.parse(input);
50
+ const textContent = (text) => [{ type: "text", text }];
51
+ const { id, hours = 24, limit = 100, includeAll = false, detail = false, } = sessionsInputSchema.parse(input);
49
52
  if (id) {
50
53
  if (!detail) {
51
54
  const metadata = await sessionStore.readSession(id);
@@ -53,7 +56,7 @@ export function registerSessionsTool(server) {
53
56
  throw new Error(`Session "${id}" not found.`);
54
57
  }
55
58
  return {
56
- content: textContent(`${metadata.createdAt} | ${metadata.status} | ${metadata.model ?? 'n/a'} | ${metadata.id}`),
59
+ content: textContent(`${metadata.createdAt} | ${metadata.status} | ${metadata.model ?? "n/a"} | ${metadata.id}`),
57
60
  structuredContent: {
58
61
  entries: [
59
62
  {
@@ -81,12 +84,18 @@ export function registerSessionsTool(server) {
81
84
  };
82
85
  }
83
86
  const metas = await sessionStore.listSessions();
84
- const { entries, truncated, total } = sessionStore.filterSessions(metas, { hours, includeAll, limit });
87
+ const { entries, truncated, total } = sessionStore.filterSessions(metas, {
88
+ hours,
89
+ includeAll,
90
+ limit,
91
+ });
85
92
  return {
86
93
  content: [
87
94
  {
88
- type: 'text',
89
- text: entries.map((entry) => `${entry.createdAt} | ${entry.status} | ${entry.model ?? 'n/a'} | ${entry.id}`).join('\n'),
95
+ type: "text",
96
+ text: entries
97
+ .map((entry) => `${entry.createdAt} | ${entry.status} | ${entry.model ?? "n/a"} | ${entry.id}`)
98
+ .join("\n"),
90
99
  },
91
100
  ],
92
101
  structuredContent: {
@@ -1,14 +1,14 @@
1
- import { z } from 'zod';
1
+ import { z } from "zod";
2
2
  export const consultInputSchema = z.object({
3
- prompt: z.string().min(1, 'Prompt is required.'),
3
+ prompt: z.string().min(1, "Prompt is required."),
4
4
  files: z.array(z.string()).default([]),
5
5
  model: z.string().optional(),
6
6
  models: z.array(z.string()).optional(),
7
- engine: z.enum(['api', 'browser']).optional(),
7
+ engine: z.enum(["api", "browser"]).optional(),
8
8
  browserModelLabel: z.string().optional(),
9
- browserAttachments: z.enum(['auto', 'never', 'always']).optional(),
9
+ browserAttachments: z.enum(["auto", "never", "always"]).optional(),
10
10
  browserBundleFiles: z.boolean().optional(),
11
- browserThinkingTime: z.enum(['light', 'standard', 'extended', 'heavy']).optional(),
11
+ browserThinkingTime: z.enum(["light", "standard", "extended", "heavy"]).optional(),
12
12
  browserKeepBrowser: z.boolean().optional(),
13
13
  search: z.boolean().optional(),
14
14
  slug: z.string().optional(),
@@ -1,25 +1,33 @@
1
- import { resolveRunOptionsFromConfig } from '../cli/runOptions.js';
2
- import { Launcher } from 'chrome-launcher';
1
+ import { resolveRunOptionsFromConfig } from "../cli/runOptions.js";
2
+ import { Launcher } from "chrome-launcher";
3
3
  export function mapConsultToRunOptions({ prompt, files, model, models, engine, search, browserAttachments, browserBundleFiles, userConfig, env = process.env, }) {
4
4
  // Normalize CLI-style inputs through the shared resolver so config/env defaults apply,
5
5
  // then overlay MCP-only overrides such as explicit search toggles.
6
6
  const mergedModels = Array.isArray(models) && models.length > 0
7
7
  ? [model, ...models].filter((entry) => Boolean(entry?.trim()))
8
8
  : models;
9
- const result = resolveRunOptionsFromConfig({ prompt, files, model, models: mergedModels, engine, userConfig, env });
10
- if (typeof search === 'boolean') {
9
+ const result = resolveRunOptionsFromConfig({
10
+ prompt,
11
+ files,
12
+ model,
13
+ models: mergedModels,
14
+ engine,
15
+ userConfig,
16
+ env,
17
+ });
18
+ if (typeof search === "boolean") {
11
19
  result.runOptions.search = search;
12
20
  }
13
21
  if (browserAttachments) {
14
22
  result.runOptions.browserAttachments = browserAttachments;
15
23
  }
16
- if (typeof browserBundleFiles === 'boolean') {
24
+ if (typeof browserBundleFiles === "boolean") {
17
25
  result.runOptions.browserBundleFiles = browserBundleFiles;
18
26
  }
19
27
  return result;
20
28
  }
21
29
  export function ensureBrowserAvailable(engine, options) {
22
- if (engine !== 'browser') {
30
+ if (engine !== "browser") {
23
31
  return null;
24
32
  }
25
33
  const remoteHost = options?.remoteHost?.trim() || process.env.ORACLE_REMOTE_HOST?.trim();
@@ -31,7 +39,7 @@ export function ensureBrowserAvailable(engine, options) {
31
39
  }
32
40
  const found = Launcher.getFirstInstallation();
33
41
  if (!found) {
34
- return 'Browser engine unavailable: no Chrome installation found and CHROME_PATH is unset.';
42
+ return "Browser engine unavailable: no Chrome installation found and CHROME_PATH is unset.";
35
43
  }
36
44
  return null;
37
45
  }
@@ -1,8 +1,8 @@
1
- import { APIConnectionError, APIConnectionTimeoutError } from 'openai';
2
- import chalk from 'chalk';
3
- import { formatElapsed } from './format.js';
4
- import { startHeartbeat } from '../heartbeat.js';
5
- import { OracleResponseError, OracleTransportError, describeTransportError, toTransportError, } from './errors.js';
1
+ import { APIConnectionError, APIConnectionTimeoutError } from "openai";
2
+ import chalk from "chalk";
3
+ import { formatElapsed } from "./format.js";
4
+ import { startHeartbeat } from "../heartbeat.js";
5
+ import { OracleResponseError, OracleTransportError, describeTransportError, toTransportError, } from "./errors.js";
6
6
  const BACKGROUND_POLL_INTERVAL_MS = 5000;
7
7
  const BACKGROUND_RETRY_BASE_MS = 3000;
8
8
  const BACKGROUND_RETRY_MAX_MS = 15000;
@@ -18,10 +18,10 @@ export async function executeBackgroundResponse(params) {
18
18
  throw transportError;
19
19
  }
20
20
  if (!initialResponse || !initialResponse.id) {
21
- throw new OracleResponseError('API did not return a response ID for the background run.', initialResponse);
21
+ throw new OracleResponseError("API did not return a response ID for the background run.", initialResponse);
22
22
  }
23
23
  const responseId = initialResponse.id;
24
- log(chalk.dim(`API scheduled background response ${responseId} (status=${initialResponse.status ?? 'unknown'}). Monitoring up to ${Math.round(maxWaitMs / 60000)} minutes for completion...`));
24
+ log(chalk.dim(`API scheduled background response ${responseId} (status=${initialResponse.status ?? "unknown"}). Monitoring up to ${Math.round(maxWaitMs / 60000)} minutes for completion...`));
25
25
  let heartbeatActive = false;
26
26
  let stopHeartbeat = null;
27
27
  const stopHeartbeatNow = () => {
@@ -66,29 +66,29 @@ async function pollBackgroundResponse(params) {
66
66
  let lastStatus = response.status;
67
67
  // biome-ignore lint/nursery/noUnnecessaryConditions: intentional polling loop.
68
68
  while (true) {
69
- const status = response.status ?? 'completed';
69
+ const status = response.status ?? "completed";
70
70
  // firstCycle toggles immediately; keep for clarity in logs.
71
71
  if (firstCycle) {
72
72
  firstCycle = false;
73
73
  log(chalk.dim(`API background response status=${status}. We'll keep retrying automatically.`));
74
74
  }
75
- else if (status !== lastStatus && status !== 'completed') {
75
+ else if (status !== lastStatus && status !== "completed") {
76
76
  log(chalk.dim(`API background response status=${status}.`));
77
77
  }
78
78
  lastStatus = status;
79
- if (status === 'completed') {
79
+ if (status === "completed") {
80
80
  return response;
81
81
  }
82
- if (status !== 'in_progress' && status !== 'queued') {
82
+ if (status !== "in_progress" && status !== "queued") {
83
83
  const detail = response.error?.message || response.incomplete_details?.reason || status;
84
84
  throw new OracleResponseError(`Response did not complete: ${detail}`, response);
85
85
  }
86
86
  if (now() - startMark >= maxWaitMs) {
87
- throw new OracleTransportError('client-timeout', 'Timed out waiting for API background response to finish.');
87
+ throw new OracleTransportError("client-timeout", "Timed out waiting for API background response to finish.");
88
88
  }
89
89
  await wait(BACKGROUND_POLL_INTERVAL_MS);
90
90
  if (now() - startMark >= maxWaitMs) {
91
- throw new OracleTransportError('client-timeout', 'Timed out waiting for API background response to finish.');
91
+ throw new OracleTransportError("client-timeout", "Timed out waiting for API background response to finish.");
92
92
  }
93
93
  const { response: nextResponse, reconnected } = await retrieveBackgroundResponseWithRetry({
94
94
  client,
@@ -100,7 +100,7 @@ async function pollBackgroundResponse(params) {
100
100
  log,
101
101
  });
102
102
  if (reconnected) {
103
- const nextStatus = nextResponse.status ?? 'in_progress';
103
+ const nextStatus = nextResponse.status ?? "in_progress";
104
104
  log(chalk.dim(`Reconnected to API background response (status=${nextStatus}). API is still working...`));
105
105
  }
106
106
  response = nextResponse;
@@ -125,7 +125,7 @@ async function retrieveBackgroundResponseWithRetry(params) {
125
125
  log(chalk.yellow(`${describeTransportError(transportError, maxWaitMs)} Retrying in ${formatElapsed(delay)}...`));
126
126
  await wait(delay);
127
127
  if (now() - startMark >= maxWaitMs) {
128
- throw new OracleTransportError('client-timeout', 'Timed out waiting for API background response to finish.');
128
+ throw new OracleTransportError("client-timeout", "Timed out waiting for API background response to finish.");
129
129
  }
130
130
  }
131
131
  }