@steipete/oracle 0.9.0 → 0.11.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 (194) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +107 -49
  3. package/dist/bin/oracle-cli.js +551 -410
  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/archiveConversation.js +224 -0
  18. package/dist/src/browser/actions/assistantResponse.js +175 -101
  19. package/dist/src/browser/actions/attachmentDataTransfer.js +49 -47
  20. package/dist/src/browser/actions/attachments.js +246 -150
  21. package/dist/src/browser/actions/deepResearch.js +662 -0
  22. package/dist/src/browser/actions/domEvents.js +2 -2
  23. package/dist/src/browser/actions/modelSelection.js +342 -119
  24. package/dist/src/browser/actions/navigation.js +183 -137
  25. package/dist/src/browser/actions/projectSources.js +491 -0
  26. package/dist/src/browser/actions/promptComposer.js +152 -91
  27. package/dist/src/browser/actions/remoteFileTransfer.js +10 -10
  28. package/dist/src/browser/actions/thinkingStatus.js +391 -0
  29. package/dist/src/browser/actions/thinkingTime.js +207 -110
  30. package/dist/src/browser/artifacts.js +150 -0
  31. package/dist/src/browser/attachRunning.js +31 -0
  32. package/dist/src/browser/chatgptImages.js +315 -0
  33. package/dist/src/browser/chromeLifecycle.js +276 -63
  34. package/dist/src/browser/config.js +59 -16
  35. package/dist/src/browser/constants.js +25 -12
  36. package/dist/src/browser/controlPlan.js +81 -0
  37. package/dist/src/browser/cookies.js +19 -19
  38. package/dist/src/browser/detect.js +250 -77
  39. package/dist/src/browser/domDebug.js +50 -1
  40. package/dist/src/browser/index.js +1559 -692
  41. package/dist/src/browser/liveTabs.js +434 -0
  42. package/dist/src/browser/modelStrategy.js +1 -1
  43. package/dist/src/browser/pageActions.js +5 -5
  44. package/dist/src/browser/policies.js +16 -13
  45. package/dist/src/browser/profileState.js +127 -42
  46. package/dist/src/browser/projectSourcesRunner.js +366 -0
  47. package/dist/src/browser/prompt.js +72 -42
  48. package/dist/src/browser/promptSummary.js +5 -5
  49. package/dist/src/browser/providerDomFlow.js +1 -1
  50. package/dist/src/browser/providers/chatgptDomProvider.js +9 -9
  51. package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +51 -42
  52. package/dist/src/browser/providers/index.js +2 -2
  53. package/dist/src/browser/reattach.js +178 -73
  54. package/dist/src/browser/reattachHelpers.js +32 -27
  55. package/dist/src/browser/sessionRunner.js +89 -25
  56. package/dist/src/browser/tabLeaseRegistry.js +182 -0
  57. package/dist/src/browser/utils.js +9 -9
  58. package/dist/src/browserMode.js +1 -1
  59. package/dist/src/cli/bridge/claudeConfig.js +24 -20
  60. package/dist/src/cli/bridge/client.js +28 -20
  61. package/dist/src/cli/bridge/codexConfig.js +16 -16
  62. package/dist/src/cli/bridge/doctor.js +47 -39
  63. package/dist/src/cli/bridge/host.js +58 -56
  64. package/dist/src/cli/browserConfig.js +102 -48
  65. package/dist/src/cli/browserDefaults.js +51 -26
  66. package/dist/src/cli/browserTabs.js +228 -0
  67. package/dist/src/cli/bundleWarnings.js +1 -1
  68. package/dist/src/cli/clipboard.js +11 -2
  69. package/dist/src/cli/detach.js +2 -2
  70. package/dist/src/cli/dryRun.js +62 -26
  71. package/dist/src/cli/duplicatePromptGuard.js +12 -4
  72. package/dist/src/cli/engine.js +9 -9
  73. package/dist/src/cli/errorUtils.js +1 -1
  74. package/dist/src/cli/fileSize.js +3 -3
  75. package/dist/src/cli/format.js +2 -2
  76. package/dist/src/cli/help.js +28 -28
  77. package/dist/src/cli/hiddenAliases.js +3 -3
  78. package/dist/src/cli/markdownBundle.js +7 -7
  79. package/dist/src/cli/markdownRenderer.js +15 -15
  80. package/dist/src/cli/notifier.js +77 -67
  81. package/dist/src/cli/options.js +131 -106
  82. package/dist/src/cli/oscUtils.js +1 -1
  83. package/dist/src/cli/projectSources.js +116 -0
  84. package/dist/src/cli/promptRequirement.js +2 -2
  85. package/dist/src/cli/renderOutput.js +1 -1
  86. package/dist/src/cli/rootAlias.js +1 -1
  87. package/dist/src/cli/runOptions.js +32 -28
  88. package/dist/src/cli/sessionCommand.js +82 -21
  89. package/dist/src/cli/sessionDisplay.js +213 -87
  90. package/dist/src/cli/sessionLineage.js +6 -2
  91. package/dist/src/cli/sessionRunner.js +149 -95
  92. package/dist/src/cli/sessionTable.js +26 -23
  93. package/dist/src/cli/stdin.js +22 -0
  94. package/dist/src/cli/tagline.js +121 -124
  95. package/dist/src/cli/tui/index.js +139 -128
  96. package/dist/src/cli/writeOutputPath.js +5 -5
  97. package/dist/src/config.js +7 -7
  98. package/dist/src/gemini-web/browserSessionManager.js +19 -15
  99. package/dist/src/gemini-web/client.js +76 -70
  100. package/dist/src/gemini-web/executionMode.js +6 -8
  101. package/dist/src/gemini-web/executor.js +98 -93
  102. package/dist/src/gemini-web/index.js +1 -1
  103. package/dist/src/mcp/consultPresets.js +19 -0
  104. package/dist/src/mcp/server.js +18 -12
  105. package/dist/src/mcp/tools/consult.js +246 -67
  106. package/dist/src/mcp/tools/projectSources.js +123 -0
  107. package/dist/src/mcp/tools/sessionResources.js +12 -12
  108. package/dist/src/mcp/tools/sessions.js +26 -17
  109. package/dist/src/mcp/types.js +12 -5
  110. package/dist/src/mcp/utils.js +21 -8
  111. package/dist/src/oracle/background.js +15 -15
  112. package/dist/src/oracle/claude.js +53 -25
  113. package/dist/src/oracle/client.js +50 -41
  114. package/dist/src/oracle/config.js +96 -66
  115. package/dist/src/oracle/errors.js +38 -38
  116. package/dist/src/oracle/files.js +55 -46
  117. package/dist/src/oracle/finishLine.js +10 -8
  118. package/dist/src/oracle/format.js +3 -3
  119. package/dist/src/oracle/gemini.js +37 -33
  120. package/dist/src/oracle/logging.js +7 -7
  121. package/dist/src/oracle/markdown.js +28 -28
  122. package/dist/src/oracle/modelResolver.js +16 -16
  123. package/dist/src/oracle/multiModelRunner.js +12 -12
  124. package/dist/src/oracle/oscProgress.js +8 -8
  125. package/dist/src/oracle/promptAssembly.js +6 -3
  126. package/dist/src/oracle/request.js +16 -13
  127. package/dist/src/oracle/run.js +160 -135
  128. package/dist/src/oracle/runUtils.js +8 -5
  129. package/dist/src/oracle/tokenEstimate.js +6 -6
  130. package/dist/src/oracle/tokenStats.js +5 -5
  131. package/dist/src/oracle/tokenStringifier.js +5 -5
  132. package/dist/src/oracle.js +12 -12
  133. package/dist/src/oracleHome.js +3 -3
  134. package/dist/src/projectSources/plan.js +27 -0
  135. package/dist/src/projectSources/url.js +23 -0
  136. package/dist/src/remote/client.js +25 -25
  137. package/dist/src/remote/health.js +20 -20
  138. package/dist/src/remote/remoteServiceConfig.js +9 -9
  139. package/dist/src/remote/server.js +129 -118
  140. package/dist/src/sessionManager.js +78 -75
  141. package/dist/src/sessionStore.js +3 -3
  142. package/dist/src/version.js +10 -10
  143. package/dist/vendor/oracle-notifier/README.md +2 -0
  144. package/package.json +67 -62
  145. package/vendor/oracle-notifier/README.md +2 -0
  146. package/dist/markdansi/types/index.js +0 -4
  147. package/dist/oracle/bin/oracle-cli.js +0 -472
  148. package/dist/oracle/src/browser/actions/assistantResponse.js +0 -471
  149. package/dist/oracle/src/browser/actions/attachments.js +0 -82
  150. package/dist/oracle/src/browser/actions/modelSelection.js +0 -190
  151. package/dist/oracle/src/browser/actions/navigation.js +0 -75
  152. package/dist/oracle/src/browser/actions/promptComposer.js +0 -167
  153. package/dist/oracle/src/browser/chromeLifecycle.js +0 -104
  154. package/dist/oracle/src/browser/config.js +0 -33
  155. package/dist/oracle/src/browser/constants.js +0 -40
  156. package/dist/oracle/src/browser/cookies.js +0 -210
  157. package/dist/oracle/src/browser/domDebug.js +0 -36
  158. package/dist/oracle/src/browser/index.js +0 -331
  159. package/dist/oracle/src/browser/pageActions.js +0 -5
  160. package/dist/oracle/src/browser/prompt.js +0 -88
  161. package/dist/oracle/src/browser/promptSummary.js +0 -20
  162. package/dist/oracle/src/browser/sessionRunner.js +0 -80
  163. package/dist/oracle/src/browser/utils.js +0 -62
  164. package/dist/oracle/src/browserMode.js +0 -1
  165. package/dist/oracle/src/cli/browserConfig.js +0 -44
  166. package/dist/oracle/src/cli/dryRun.js +0 -59
  167. package/dist/oracle/src/cli/engine.js +0 -17
  168. package/dist/oracle/src/cli/errorUtils.js +0 -9
  169. package/dist/oracle/src/cli/help.js +0 -70
  170. package/dist/oracle/src/cli/markdownRenderer.js +0 -15
  171. package/dist/oracle/src/cli/options.js +0 -103
  172. package/dist/oracle/src/cli/promptRequirement.js +0 -14
  173. package/dist/oracle/src/cli/rootAlias.js +0 -30
  174. package/dist/oracle/src/cli/sessionCommand.js +0 -77
  175. package/dist/oracle/src/cli/sessionDisplay.js +0 -270
  176. package/dist/oracle/src/cli/sessionRunner.js +0 -94
  177. package/dist/oracle/src/heartbeat.js +0 -43
  178. package/dist/oracle/src/oracle/client.js +0 -48
  179. package/dist/oracle/src/oracle/config.js +0 -29
  180. package/dist/oracle/src/oracle/errors.js +0 -101
  181. package/dist/oracle/src/oracle/files.js +0 -220
  182. package/dist/oracle/src/oracle/format.js +0 -33
  183. package/dist/oracle/src/oracle/fsAdapter.js +0 -7
  184. package/dist/oracle/src/oracle/oscProgress.js +0 -60
  185. package/dist/oracle/src/oracle/request.js +0 -48
  186. package/dist/oracle/src/oracle/run.js +0 -444
  187. package/dist/oracle/src/oracle/tokenStats.js +0 -39
  188. package/dist/oracle/src/oracle/types.js +0 -1
  189. package/dist/oracle/src/oracle.js +0 -9
  190. package/dist/oracle/src/sessionManager.js +0 -205
  191. package/dist/oracle/src/version.js +0 -39
  192. package/dist/scripts/chrome/browser-tools.js +0 -295
  193. package/dist/src/browser/profileSync.js +0 -141
  194. /package/dist/{oracle/src/browser → src/projectSources}/types.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,85 @@ 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 { runDryRunSummary } from "../../cli/dryRun.js";
22
+ import { CHATGPT_URL } from "../../browser/constants.js";
23
+ import { CONSULT_PRESETS, consultInputSchema } from "../types.js";
24
+ import { applyConsultPreset } from "../consultPresets.js";
25
+ import { loadUserConfig } from "../../config.js";
26
+ import { resolveNotificationSettings } from "../../cli/notifier.js";
27
+ import { mapModelToBrowserLabel, resolveBrowserModelLabel } from "../../cli/browserConfig.js";
26
28
  // Use raw shapes so the MCP SDK (with its bundled Zod) wraps them and emits valid JSON Schema.
27
29
  const consultInputShape = {
28
- prompt: z
29
- .string()
30
- .min(1, 'Prompt is required.')
31
- .describe('User prompt to run.'),
30
+ preset: z
31
+ .enum(CONSULT_PRESETS)
32
+ .optional()
33
+ .describe('Optional MCP convenience preset. "chatgpt-pro-heavy" selects ChatGPT browser mode, the current Pro model alias, and Pro Extended thinking unless overridden.'),
34
+ prompt: z.string().min(1, "Prompt is required.").describe("User prompt to run."),
32
35
  files: z
33
36
  .array(z.string())
34
37
  .default([])
35
- .describe('Optional file paths or glob patterns (like the CLI `--file`). Resolved relative to the MCP server working directory.'),
38
+ .describe("Optional file paths or glob patterns (like the CLI `--file`). Resolved relative to the MCP server working directory."),
36
39
  model: z
37
40
  .string()
38
41
  .optional()
39
- .describe('Single model name/label. Prefer setting `engine` explicitly to avoid default surprises.'),
42
+ .describe("Single model name/label. If `engine` is omitted, Oracle follows CLI defaults: config/ORACLE_ENGINE first, then `api` when OPENAI_API_KEY is set, otherwise `browser`. Prefer setting `engine` explicitly to avoid default surprises."),
40
43
  models: z
41
44
  .array(z.string())
42
45
  .optional()
43
- .describe('Multi-model fan-out (API engine only). Cannot be combined with browser automation.'),
46
+ .describe("Multi-model fan-out (API engine only). Cannot be combined with browser automation."),
44
47
  engine: z
45
- .enum(['api', 'browser'])
48
+ .enum(["api", "browser"])
46
49
  .optional()
47
- .describe('Execution engine. `api` uses OpenAI/other providers. `browser` automates the ChatGPT web UI (supports attachments and ChatGPT-only model labels).'),
50
+ .describe("Execution engine. `api` uses OpenAI/other providers. `browser` automates the ChatGPT web UI (supports attachments and ChatGPT-only model labels). When omitted, Oracle follows CLI defaults: config/ORACLE_ENGINE first, then `api` when OPENAI_API_KEY is set, otherwise `browser`."),
48
51
  browserModelLabel: z
49
52
  .string()
50
53
  .optional()
51
54
  .describe('Browser-only: explicit ChatGPT UI label to select (overrides model mapping). Example: "GPT-5.2 Thinking".'),
52
55
  browserAttachments: z
53
- .enum(['auto', 'never', 'always'])
56
+ .enum(["auto", "never", "always"])
54
57
  .optional()
55
58
  .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
59
  browserBundleFiles: z
57
60
  .boolean()
58
61
  .optional()
59
- .describe('Browser-only: bundle many files into a single upload (helps with upload limits).'),
62
+ .describe("Browser-only: bundle many files into a single upload (helps with upload limits)."),
60
63
  browserThinkingTime: z
61
- .enum(['light', 'standard', 'extended', 'heavy'])
64
+ .enum(["light", "standard", "extended", "heavy"])
65
+ .optional()
66
+ .describe("Browser-only: set ChatGPT thinking time when supported by the chosen model."),
67
+ browserModelStrategy: z
68
+ .enum(["select", "current", "ignore"])
62
69
  .optional()
63
- .describe('Browser-only: set ChatGPT thinking time when supported by the chosen model.'),
70
+ .describe("Browser-only: model picker strategy. Mirrors the CLI --browser-model-strategy flag."),
71
+ browserResearchMode: z
72
+ .enum(["deep"])
73
+ .optional()
74
+ .describe("Browser-only: activate ChatGPT Deep Research mode for broad web research."),
75
+ browserArchive: z
76
+ .enum(["auto", "always", "never"])
77
+ .optional()
78
+ .describe('Browser-only: archive completed ChatGPT conversations after local artifacts are saved. "auto" archives successful non-project one-shots only.'),
79
+ browserFollowUps: z
80
+ .array(z.string())
81
+ .optional()
82
+ .describe("Browser-only: additional prompts to submit sequentially in the same ChatGPT conversation after the initial answer."),
64
83
  browserKeepBrowser: z
65
84
  .boolean()
66
85
  .optional()
67
- .describe('Browser-only: keep Chrome running after completion (useful for debugging).'),
86
+ .describe("Browser-only: keep Chrome running after completion (useful for debugging)."),
87
+ dryRun: z
88
+ .boolean()
89
+ .optional()
90
+ .describe("Preview the resolved Oracle run without creating a session or touching the browser."),
68
91
  search: z
69
92
  .boolean()
70
93
  .optional()
71
- .describe('API-only: enable/disable the provider search tool (browser engine ignores this).'),
94
+ .describe("API-only: enable/disable the provider search tool (browser engine ignores this)."),
72
95
  slug: z
73
96
  .string()
74
97
  .optional()
75
- .describe('Optional human-friendly session id (used for later `oracle sessions` lookups).'),
98
+ .describe("Optional human-friendly session id (used for later `oracle sessions` lookups)."),
76
99
  };
77
100
  const consultModelSummaryShape = z.object({
78
101
  model: z.string(),
@@ -103,10 +126,34 @@ const consultModelSummaryShape = z.object({
103
126
  .optional(),
104
127
  logPath: z.string().optional(),
105
128
  });
129
+ const consultDryRunResolvedShape = z.object({
130
+ resolvedEngine: z.enum(["api", "browser"]),
131
+ model: z.string(),
132
+ models: z.array(z.string()).optional(),
133
+ files: z.array(z.string()),
134
+ followUpCount: z.number(),
135
+ browser: z
136
+ .object({
137
+ desiredModel: z.string().nullable().optional(),
138
+ thinkingTime: z.string().nullable().optional(),
139
+ modelStrategy: z.string().nullable().optional(),
140
+ researchMode: z.string().nullable().optional(),
141
+ attachments: z.string().optional(),
142
+ bundleFiles: z.boolean().optional(),
143
+ keepBrowser: z.boolean().optional(),
144
+ manualLogin: z.boolean().optional(),
145
+ profileDir: z.string().nullable().optional(),
146
+ chatgptUrl: z.string().nullable().optional(),
147
+ })
148
+ .optional(),
149
+ guidance: z.array(z.string()),
150
+ });
106
151
  const consultOutputShape = {
107
- sessionId: z.string(),
152
+ sessionId: z.string().optional(),
108
153
  status: z.string(),
109
154
  output: z.string(),
155
+ dryRun: z.boolean().optional(),
156
+ resolved: consultDryRunResolvedShape.optional(),
110
157
  models: z.array(consultModelSummaryShape).optional(),
111
158
  };
112
159
  export function summarizeModelRunsForConsult(runs) {
@@ -129,7 +176,7 @@ export function summarizeModelRunsForConsult(runs) {
129
176
  : undefined;
130
177
  return {
131
178
  model: run.model,
132
- status: run.status ?? 'unknown',
179
+ status: run.status ?? "unknown",
133
180
  startedAt: run.startedAt,
134
181
  completedAt: run.completedAt,
135
182
  usage: run.usage,
@@ -139,17 +186,17 @@ export function summarizeModelRunsForConsult(runs) {
139
186
  };
140
187
  });
141
188
  }
142
- export function buildConsultBrowserConfig({ userConfig, env, runModel, inputModel, browserModelLabel, browserThinkingTime, browserKeepBrowser, }) {
189
+ export function buildConsultBrowserConfig({ userConfig, env, runModel, inputModel, browserModelLabel, browserThinkingTime, browserModelStrategy, browserResearchMode, browserArchive, browserKeepBrowser, }) {
143
190
  const configuredBrowser = userConfig.browser ?? {};
144
- const envProfileDir = (env.ORACLE_BROWSER_PROFILE_DIR ?? '').trim();
191
+ const envProfileDir = (env.ORACLE_BROWSER_PROFILE_DIR ?? "").trim();
145
192
  const hasProfileDir = envProfileDir.length > 0;
146
193
  const preferredLabel = (browserModelLabel ?? inputModel)?.trim();
147
- const isChatGptModel = runModel.startsWith('gpt-') && !runModel.includes('codex');
194
+ const isChatGptModel = runModel.startsWith("gpt-") && !runModel.includes("codex");
148
195
  const desiredModelLabel = isChatGptModel
149
196
  ? mapModelToBrowserLabel(runModel)
150
197
  : resolveBrowserModelLabel(preferredLabel, runModel);
151
198
  const configuredUrl = configuredBrowser.chatgptUrl ?? configuredBrowser.url ?? CHATGPT_URL;
152
- const manualLogin = hasProfileDir ? true : configuredBrowser.manualLogin ?? false;
199
+ const manualLogin = hasProfileDir ? true : (configuredBrowser.manualLogin ?? false);
153
200
  return {
154
201
  ...configuredBrowser,
155
202
  url: configuredUrl,
@@ -159,21 +206,112 @@ export function buildConsultBrowserConfig({ userConfig, env, runModel, inputMode
159
206
  hideWindow: configuredBrowser.hideWindow ?? false,
160
207
  keepBrowser: browserKeepBrowser ?? configuredBrowser.keepBrowser ?? false,
161
208
  manualLogin,
162
- manualLoginProfileDir: manualLogin ? ((envProfileDir || configuredBrowser.manualLoginProfileDir) ?? null) : null,
209
+ manualLoginProfileDir: manualLogin
210
+ ? ((envProfileDir || configuredBrowser.manualLoginProfileDir) ?? null)
211
+ : null,
163
212
  thinkingTime: browserThinkingTime ?? configuredBrowser.thinkingTime,
213
+ modelStrategy: browserModelStrategy ?? configuredBrowser.modelStrategy,
214
+ researchMode: browserResearchMode ?? configuredBrowser.researchMode,
215
+ archiveConversations: browserArchive ?? configuredBrowser.archiveConversations,
164
216
  desiredModel: desiredModelLabel || mapModelToBrowserLabel(runModel),
165
217
  };
166
218
  }
219
+ export function buildConsultDryRunResolved({ resolvedEngine, runOptions, browserConfig, }) {
220
+ const guidance = [];
221
+ const followUpCount = runOptions.browserFollowUps?.filter((entry) => entry.trim()).length ?? 0;
222
+ if (resolvedEngine === "api") {
223
+ guidance.push('API engine requires provider credentials. If the operator has ChatGPT Pro but no API key, retry with engine:"browser" or preset:"chatgpt-pro-heavy".');
224
+ }
225
+ if (resolvedEngine === "browser") {
226
+ guidance.push("Browser engine uses the signed-in ChatGPT profile; run dryRun:true before live use.");
227
+ }
228
+ const desiredModel = browserConfig?.desiredModel ?? null;
229
+ const thinkingTime = browserConfig?.thinkingTime ?? null;
230
+ if (runOptions.model === "gpt-5.5-pro" && thinkingTime === "heavy") {
231
+ guidance.push('gpt-5.5-pro should normally use Pro Extended. Use model:"gpt-5.5" with browserThinkingTime:"heavy" only when you explicitly want Thinking Heavy.');
232
+ }
233
+ const chatgptUrl = browserConfig?.chatgptUrl ?? browserConfig?.url ?? null;
234
+ if (chatgptUrl?.includes("/project")) {
235
+ guidance.push("This ChatGPT project URL is persistent. Project Sources should be mutated only by the project_sources tool with confirmMutation:true.");
236
+ }
237
+ if (followUpCount > 0) {
238
+ guidance.push("This is a multi-turn browser consult; all follow-ups stay in one ChatGPT conversation.");
239
+ }
240
+ return {
241
+ resolvedEngine,
242
+ model: runOptions.model,
243
+ models: runOptions.models,
244
+ files: runOptions.file ?? [],
245
+ followUpCount,
246
+ browser: resolvedEngine === "browser"
247
+ ? {
248
+ desiredModel,
249
+ thinkingTime,
250
+ modelStrategy: browserConfig?.modelStrategy ?? null,
251
+ researchMode: browserConfig?.researchMode ?? null,
252
+ attachments: runOptions.browserAttachments,
253
+ bundleFiles: runOptions.browserBundleFiles,
254
+ keepBrowser: browserConfig?.keepBrowser,
255
+ manualLogin: browserConfig?.manualLogin,
256
+ profileDir: browserConfig?.manualLoginProfileDir ?? null,
257
+ chatgptUrl,
258
+ }
259
+ : undefined,
260
+ guidance,
261
+ };
262
+ }
263
+ export function formatConsultDryRunResolved(details) {
264
+ const lines = [
265
+ "[dry-run] MCP resolved request:",
266
+ ` engine: ${details.resolvedEngine}`,
267
+ ` model: ${details.model}`,
268
+ ];
269
+ if (details.models && details.models.length > 0) {
270
+ lines.push(` models: ${details.models.join(", ")}`);
271
+ }
272
+ lines.push(` files: ${details.files.length}`);
273
+ if (details.browser) {
274
+ lines.push(` browser desired model: ${details.browser.desiredModel ?? "(default)"}`);
275
+ lines.push(` browser thinking time: ${details.browser.thinkingTime ?? "(default)"}`);
276
+ lines.push(` browser model strategy: ${details.browser.modelStrategy ?? "(default)"}`);
277
+ lines.push(` browser research mode: ${details.browser.researchMode ?? "off"}`);
278
+ lines.push(` browser attachments: ${details.browser.attachments ?? "auto"}`);
279
+ lines.push(` browser bundle files: ${details.browser.bundleFiles ? "yes" : "no"}`);
280
+ lines.push(` browser keep browser: ${details.browser.keepBrowser ? "yes" : "no"}`);
281
+ lines.push(` browser manual login: ${details.browser.manualLogin ? "yes" : "no"}`);
282
+ if (details.browser.profileDir) {
283
+ lines.push(` browser profile: ${details.browser.profileDir}`);
284
+ }
285
+ if (details.browser.chatgptUrl) {
286
+ lines.push(` ChatGPT URL: ${details.browser.chatgptUrl}`);
287
+ }
288
+ }
289
+ lines.push(` follow-ups: ${details.followUpCount}`);
290
+ for (const guidance of details.guidance) {
291
+ lines.push(` guidance: ${guidance}`);
292
+ }
293
+ return lines;
294
+ }
167
295
  export function registerConsultTool(server) {
168
- server.registerTool('consult', {
169
- title: 'Run an oracle session',
170
- 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).',
296
+ server.registerTool("consult", {
297
+ title: "Run an oracle session",
298
+ 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. For browser-based image/file uploads, set `browserAttachments:"always"`. 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).',
171
299
  // Cast to any to satisfy SDK typings across differing Zod versions.
172
300
  inputSchema: consultInputShape,
173
301
  outputSchema: consultOutputShape,
174
302
  }, async (input) => {
175
- const textContent = (text) => [{ type: 'text', text }];
176
- const { prompt, files, model, models, engine, search, browserModelLabel, browserAttachments, browserBundleFiles, browserThinkingTime, browserKeepBrowser, slug, } = consultInputSchema.parse(input);
303
+ const textContent = (text) => [{ type: "text", text }];
304
+ let parsedInput;
305
+ try {
306
+ parsedInput = applyConsultPreset(consultInputSchema.parse(input));
307
+ }
308
+ catch (error) {
309
+ return {
310
+ isError: true,
311
+ content: textContent(error instanceof Error ? error.message : String(error)),
312
+ };
313
+ }
314
+ const { prompt, files, model, models, engine, search, browserModelLabel, browserAttachments, browserBundleFiles, browserThinkingTime, browserModelStrategy, browserResearchMode, browserArchive, browserFollowUps, browserKeepBrowser, dryRun, slug, } = parsedInput;
177
315
  const { config: userConfig } = await loadUserConfig();
178
316
  const { runOptions, resolvedEngine } = mapConsultToRunOptions({
179
317
  prompt,
@@ -184,20 +322,77 @@ export function registerConsultTool(server) {
184
322
  search,
185
323
  browserAttachments,
186
324
  browserBundleFiles,
325
+ browserFollowUps,
187
326
  userConfig,
188
327
  env: process.env,
189
328
  });
190
329
  const cwd = process.cwd();
330
+ const sendLog = (text, level = "info") => server.server
331
+ .sendLoggingMessage(LoggingMessageNotificationParamsSchema.parse({
332
+ level,
333
+ data: { text, bytes: Buffer.byteLength(text, "utf8") },
334
+ }))
335
+ .catch(() => { });
191
336
  const resolvedRemote = resolveRemoteServiceConfig({ userConfig, env: process.env });
192
- const browserGuard = ensureBrowserAvailable(resolvedEngine, { remoteHost: resolvedRemote.host });
193
- if (resolvedEngine === 'browser' && browserGuard) {
337
+ let browserConfig;
338
+ if (resolvedEngine === "browser") {
339
+ browserConfig = buildConsultBrowserConfig({
340
+ userConfig,
341
+ env: process.env,
342
+ runModel: runOptions.model,
343
+ inputModel: model,
344
+ browserModelLabel,
345
+ browserThinkingTime,
346
+ browserModelStrategy,
347
+ browserResearchMode,
348
+ browserArchive,
349
+ browserKeepBrowser,
350
+ });
351
+ }
352
+ if (dryRun) {
353
+ const lines = [];
354
+ const log = (line) => {
355
+ lines.push(line);
356
+ sendLog(line);
357
+ };
358
+ const resolved = buildConsultDryRunResolved({
359
+ resolvedEngine,
360
+ runOptions,
361
+ browserConfig,
362
+ });
363
+ await runDryRunSummary({
364
+ engine: resolvedEngine,
365
+ runOptions,
366
+ cwd,
367
+ version: getCliVersion(),
368
+ log,
369
+ browserConfig,
370
+ });
371
+ for (const line of formatConsultDryRunResolved(resolved)) {
372
+ log(line);
373
+ }
374
+ const output = lines.join("\n").trim();
375
+ return {
376
+ content: textContent(output),
377
+ structuredContent: {
378
+ status: "dry-run",
379
+ output,
380
+ dryRun: true,
381
+ resolved,
382
+ },
383
+ };
384
+ }
385
+ const browserGuard = ensureBrowserAvailable(resolvedEngine, {
386
+ remoteHost: resolvedRemote.host,
387
+ });
388
+ if (resolvedEngine === "browser" && browserGuard) {
194
389
  return {
195
390
  isError: true,
196
391
  content: textContent(browserGuard),
197
392
  };
198
393
  }
199
394
  let browserDeps;
200
- if (resolvedEngine === 'browser' && resolvedRemote.host) {
395
+ if (resolvedEngine === "browser" && resolvedRemote.host) {
201
396
  if (!resolvedRemote.token) {
202
397
  return {
203
398
  isError: true,
@@ -205,21 +400,12 @@ export function registerConsultTool(server) {
205
400
  };
206
401
  }
207
402
  browserDeps = {
208
- executeBrowser: createRemoteBrowserExecutor({ host: resolvedRemote.host, token: resolvedRemote.token }),
403
+ executeBrowser: createRemoteBrowserExecutor({
404
+ host: resolvedRemote.host,
405
+ token: resolvedRemote.token,
406
+ }),
209
407
  };
210
408
  }
211
- let browserConfig;
212
- if (resolvedEngine === 'browser') {
213
- browserConfig = buildConsultBrowserConfig({
214
- userConfig,
215
- env: process.env,
216
- runModel: runOptions.model,
217
- inputModel: model,
218
- browserModelLabel,
219
- browserThinkingTime,
220
- browserKeepBrowser,
221
- });
222
- }
223
409
  const notifications = resolveNotificationSettings({
224
410
  cliNotify: undefined,
225
411
  cliNotifySound: undefined,
@@ -234,13 +420,6 @@ export function registerConsultTool(server) {
234
420
  waitPreference: true,
235
421
  }, cwd, notifications);
236
422
  const logWriter = sessionStore.createLogWriter(sessionMeta.id);
237
- // Best-effort: emit MCP logging notifications for live chunks but never block the run.
238
- const sendLog = (text, level = 'info') => server.server
239
- .sendLoggingMessage(LoggingMessageNotificationParamsSchema.parse({
240
- level,
241
- data: { text, bytes: Buffer.byteLength(text, 'utf8') },
242
- }))
243
- .catch(() => { });
244
423
  // Stream logs to both the session log and MCP logging notifications, but avoid buffering in memory
245
424
  const log = (line) => {
246
425
  logWriter.logLine(line);
@@ -250,7 +429,7 @@ export function registerConsultTool(server) {
250
429
  };
251
430
  const write = (chunk) => {
252
431
  logWriter.writeChunk(chunk);
253
- sendLog(chunk, 'debug');
432
+ sendLog(chunk, "debug");
254
433
  return true;
255
434
  };
256
435
  try {
@@ -284,11 +463,11 @@ export function registerConsultTool(server) {
284
463
  const logTail = await readSessionLogTail(sessionMeta.id, 4000);
285
464
  const modelsSummary = summarizeModelRunsForConsult(finalMeta.models);
286
465
  return {
287
- content: textContent([summary, logTail || '(log empty)'].join('\n').trim()),
466
+ content: textContent([summary, logTail || "(log empty)"].join("\n").trim()),
288
467
  structuredContent: {
289
468
  sessionId: sessionMeta.id,
290
469
  status: finalMeta.status,
291
- output: logTail ?? '',
470
+ output: logTail ?? "",
292
471
  models: modelsSummary,
293
472
  },
294
473
  };
@@ -0,0 +1,123 @@
1
+ import { z } from "zod";
2
+ import { loadUserConfig } from "../../config.js";
3
+ import { resolveRemoteServiceConfig } from "../../remote/remoteServiceConfig.js";
4
+ import { runBrowserProjectSources } from "../../browser/projectSourcesRunner.js";
5
+ import { normalizeProjectSourcesUrl } from "../../projectSources/url.js";
6
+ import { buildProjectSourcesBrowserConfig, resolveProjectSourceFiles, } from "../../cli/projectSources.js";
7
+ import { resolveConfiguredMaxFileSizeBytes } from "../../cli/fileSize.js";
8
+ const projectSourceEntryShape = z.object({
9
+ name: z.string(),
10
+ index: z.number(),
11
+ status: z.enum(["ready", "processing", "unknown"]).optional(),
12
+ });
13
+ const projectSourceUploadPlanShape = z.object({
14
+ path: z.string(),
15
+ displayPath: z.string(),
16
+ name: z.string(),
17
+ sizeBytes: z.number().optional(),
18
+ batch: z.number(),
19
+ });
20
+ const projectSourcesInputShape = {
21
+ operation: z
22
+ .enum(["list", "add"])
23
+ .describe("Project Sources operation. v1 intentionally supports only non-destructive list/add."),
24
+ chatgptUrl: z
25
+ .string()
26
+ .optional()
27
+ .describe("ChatGPT project URL ending in /project. Falls back to browser.chatgptUrl config."),
28
+ files: z
29
+ .array(z.string())
30
+ .default([])
31
+ .describe("Local file paths or globs to add as persistent ChatGPT Project Sources."),
32
+ dryRun: z
33
+ .boolean()
34
+ .optional()
35
+ .describe("Validate files and return an upload plan without touching the browser."),
36
+ confirmMutation: z
37
+ .boolean()
38
+ .optional()
39
+ .describe("Required for mutating add operations so agents do not modify project state accidentally."),
40
+ browserKeepBrowser: z.boolean().optional().describe("Keep Chrome running after completion."),
41
+ };
42
+ const projectSourcesOutputShape = {
43
+ status: z.enum(["ok", "dry-run"]),
44
+ operation: z.enum(["list", "add"]),
45
+ projectUrl: z.string(),
46
+ dryRun: z.boolean(),
47
+ sourcesBefore: z.array(projectSourceEntryShape).optional(),
48
+ sourcesAfter: z.array(projectSourceEntryShape).optional(),
49
+ plannedUploads: z.array(projectSourceUploadPlanShape).optional(),
50
+ added: z.array(projectSourceEntryShape).optional(),
51
+ warnings: z.array(z.string()),
52
+ tookMs: z.number(),
53
+ };
54
+ const projectSourcesInputSchema = z.object(projectSourcesInputShape);
55
+ export function registerProjectSourcesTool(server) {
56
+ server.registerTool("project_sources", {
57
+ title: "Manage ChatGPT Project Sources",
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) => {
62
+ const textContent = (text) => [{ type: "text", text }];
63
+ let parsed;
64
+ try {
65
+ parsed = projectSourcesInputSchema.parse(input);
66
+ }
67
+ catch (error) {
68
+ return {
69
+ isError: true,
70
+ content: textContent(error instanceof Error ? error.message : String(error)),
71
+ };
72
+ }
73
+ const { config: userConfig } = await loadUserConfig();
74
+ const resolvedRemote = resolveRemoteServiceConfig({ userConfig, env: process.env });
75
+ if (resolvedRemote.host) {
76
+ return {
77
+ isError: true,
78
+ content: textContent("project_sources v1 must run on the signed-in browser host; remote oracle serve support is not enabled yet."),
79
+ };
80
+ }
81
+ const projectUrl = normalizeProjectSourcesUrl(parsed.chatgptUrl ?? userConfig.browser?.chatgptUrl ?? userConfig.browser?.url ?? "");
82
+ if (parsed.operation === "add" && !parsed.dryRun && parsed.confirmMutation !== true) {
83
+ return {
84
+ isError: true,
85
+ content: textContent("project_sources add modifies persistent ChatGPT Project Sources. Retry with `confirmMutation: true` or use `dryRun: true` first."),
86
+ };
87
+ }
88
+ const maxFileSizeBytes = resolveConfiguredMaxFileSizeBytes(userConfig, process.env);
89
+ const files = parsed.operation === "add"
90
+ ? await resolveProjectSourceFiles(parsed.files ?? [], {
91
+ cwd: process.cwd(),
92
+ maxFileSizeBytes,
93
+ })
94
+ : [];
95
+ const browserConfig = await buildProjectSourcesBrowserConfig({
96
+ options: {
97
+ chatgptUrl: projectUrl,
98
+ browserKeepBrowser: parsed.browserKeepBrowser,
99
+ },
100
+ projectUrl,
101
+ configuredBrowser: userConfig.browser ?? {},
102
+ });
103
+ const result = await runBrowserProjectSources({
104
+ operation: parsed.operation,
105
+ chatgptUrl: projectUrl,
106
+ files,
107
+ dryRun: parsed.dryRun,
108
+ config: browserConfig,
109
+ log: (message) => {
110
+ server.server
111
+ .sendLoggingMessage({ level: "info", data: { text: message } })
112
+ .catch(() => undefined);
113
+ },
114
+ });
115
+ const output = result.status === "dry-run"
116
+ ? `Project Sources ${result.operation} dry run: ${result.plannedUploads?.length ?? 0} planned upload(s).`
117
+ : `Project Sources ${result.operation} completed: ${result.sourcesAfter?.length ?? 0} source(s).`;
118
+ return {
119
+ content: textContent(output),
120
+ structuredContent: result,
121
+ };
122
+ });
123
+ }
@@ -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
  {