@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,57 +1,62 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { CHATGPT_URL, DEFAULT_MODEL_STRATEGY, DEFAULT_MODEL_TARGET, isTemporaryChatUrl, normalizeChatgptUrl, parseDuration } from '../browserMode.js';
4
- import { normalizeBrowserModelStrategy } from '../browser/modelStrategy.js';
5
- import { getOracleHomeDir } from '../oracleHome.js';
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { CHATGPT_URL, DEFAULT_MODEL_STRATEGY, DEFAULT_MODEL_TARGET, isTemporaryChatUrl, normalizeChatgptUrl, parseDuration, } from "../browserMode.js";
4
+ import { normalizeBrowserModelStrategy } from "../browser/modelStrategy.js";
5
+ import { getOracleHomeDir } from "../oracleHome.js";
6
6
  const DEFAULT_BROWSER_TIMEOUT_MS = 1_200_000;
7
7
  const DEFAULT_BROWSER_INPUT_TIMEOUT_MS = 60_000;
8
8
  const DEFAULT_BROWSER_RECHECK_TIMEOUT_MS = 120_000;
9
9
  const DEFAULT_BROWSER_AUTO_REATTACH_TIMEOUT_MS = 120_000;
10
- const DEFAULT_CHROME_PROFILE = 'Default';
10
+ const DEFAULT_CHROME_PROFILE = "Default";
11
11
  // Ordered array: most specific models first to ensure correct selection.
12
12
  // The browser label is passed to the model picker which fuzzy-matches against ChatGPT's UI.
13
13
  const BROWSER_MODEL_LABELS = [
14
14
  // Most specific first (e.g., "gpt-5.2-thinking" before "gpt-5.2")
15
- ['gpt-5.4-pro', 'GPT-5.4 Pro'],
16
- ['gpt-5.2-thinking', 'GPT-5.2 Thinking'],
17
- ['gpt-5.2-instant', 'GPT-5.2 Instant'],
18
- ['gpt-5.2-pro', 'GPT-5.4 Pro'],
19
- ['gpt-5.1-pro', 'GPT-5.4 Pro'],
20
- ['gpt-5-pro', 'GPT-5.4 Pro'],
15
+ ["gpt-5.5-pro", "GPT-5.5 Pro"],
16
+ ["gpt-5.5", "Thinking 5.5"],
17
+ ["gpt-5.4-pro", "GPT-5.4 Pro"],
18
+ ["gpt-5.2-thinking", "GPT-5.2 Thinking"],
19
+ ["gpt-5.2-instant", "GPT-5.2 Instant"],
20
+ ["gpt-5.2-pro", "GPT-5.5 Pro"],
21
+ ["gpt-5.1-pro", "GPT-5.5 Pro"],
22
+ ["gpt-5-pro", "GPT-5.5 Pro"],
21
23
  // Base models last (least specific)
22
- ['gpt-5.4', 'Thinking 5.4'],
23
- ['gpt-5.2', 'GPT-5.2'], // Selects "Auto" in ChatGPT UI
24
- ['gpt-5.1', 'GPT-5.2'], // Legacy alias → Auto
25
- ['gemini-3-pro', 'Gemini 3 Pro'],
26
- ['gemini-3-pro-deep-think', 'gemini-3-deep-think'],
24
+ ["gpt-5.4", "Thinking 5.4"],
25
+ ["gpt-5.2", "GPT-5.2"], // Selects "Auto" in ChatGPT UI
26
+ ["gpt-5.1", "GPT-5.2"], // Legacy alias → Auto
27
+ ["gemini-3-pro", "Gemini 3 Pro"],
28
+ ["gemini-3-pro-deep-think", "gemini-3-deep-think"],
27
29
  ];
28
30
  export function normalizeChatGptModelForBrowser(model) {
29
31
  const normalized = model.toLowerCase();
30
- if (!normalized.startsWith('gpt-') || normalized.includes('codex')) {
32
+ if (!normalized.startsWith("gpt-") || normalized.includes("codex")) {
31
33
  return model;
32
34
  }
33
- if (normalized === 'gpt-5.4-pro' || normalized === 'gpt-5.4') {
35
+ if (normalized === "gpt-5.5-pro" ||
36
+ normalized === "gpt-5.5" ||
37
+ normalized === "gpt-5.4-pro" ||
38
+ normalized === "gpt-5.4") {
34
39
  return normalized;
35
40
  }
36
41
  // Pro variants: resolve to the latest Pro model in ChatGPT.
37
- if (normalized === 'gpt-5-pro' || normalized === 'gpt-5.1-pro' || normalized === 'gpt-5.2-pro') {
38
- return 'gpt-5.4-pro';
42
+ if (normalized === "gpt-5-pro" || normalized === "gpt-5.1-pro" || normalized === "gpt-5.2-pro") {
43
+ return "gpt-5.5-pro";
39
44
  }
40
45
  // Explicit model variants: keep as-is (they have their own browser labels)
41
- if (normalized === 'gpt-5.2-thinking' || normalized === 'gpt-5.2-instant') {
46
+ if (normalized === "gpt-5.2-thinking" || normalized === "gpt-5.2-instant") {
42
47
  return normalized;
43
48
  }
44
49
  // Legacy aliases: map to base GPT-5.2 (Auto)
45
- if (normalized === 'gpt-5.1') {
46
- return 'gpt-5.2';
50
+ if (normalized === "gpt-5.1") {
51
+ return "gpt-5.2";
47
52
  }
48
53
  return model;
49
54
  }
50
55
  export async function buildBrowserConfig(options) {
51
56
  const desiredModelOverride = options.browserModelLabel?.trim();
52
- const normalizedOverride = desiredModelOverride?.toLowerCase() ?? '';
57
+ const normalizedOverride = desiredModelOverride?.toLowerCase() ?? "";
53
58
  const baseModel = options.model.toLowerCase();
54
- const isChatGptModel = baseModel.startsWith('gpt-') && !baseModel.includes('codex');
59
+ const isChatGptModel = baseModel.startsWith("gpt-") && !baseModel.includes("codex");
55
60
  const shouldUseOverride = !isChatGptModel && normalizedOverride.length > 0 && normalizedOverride !== baseModel;
56
61
  const modelStrategy = normalizeBrowserModelStrategy(options.browserModelStrategy) ?? DEFAULT_MODEL_STRATEGY;
57
62
  const cookieNames = parseCookieNames(options.browserCookieNames ?? process.env.ORACLE_BROWSER_COOKIE_NAMES);
@@ -62,13 +67,18 @@ export async function buildBrowserConfig(options) {
62
67
  envFile: process.env.ORACLE_BROWSER_COOKIES_FILE,
63
68
  cwd: process.cwd(),
64
69
  });
65
- if (inline?.source?.startsWith('home:') && options.browserNoCookieSync !== true) {
70
+ if (inline?.source?.startsWith("home:") && options.browserNoCookieSync !== true) {
66
71
  inline = undefined;
67
72
  }
68
73
  let remoteChrome;
69
74
  if (options.remoteChrome) {
70
75
  remoteChrome = parseRemoteChromeTarget(options.remoteChrome);
71
76
  }
77
+ const attachRunning = options.browserAttachRunning === true;
78
+ validateAttachRunningOptions(options, {
79
+ attachRunning,
80
+ hasInlineCookies: Boolean(inline?.cookies),
81
+ });
72
82
  const rawUrl = options.chatgptUrl ?? options.browserUrl;
73
83
  const url = rawUrl ? normalizeChatgptUrl(rawUrl, CHATGPT_URL) : undefined;
74
84
  const desiredModel = isChatGptModel
@@ -76,17 +86,23 @@ export async function buildBrowserConfig(options) {
76
86
  : shouldUseOverride
77
87
  ? desiredModelOverride
78
88
  : mapModelToBrowserLabel(options.model);
79
- if (modelStrategy === 'select' && url && isTemporaryChatUrl(url) && /\bpro\b/i.test(desiredModel ?? '')) {
80
- throw new Error('Temporary Chat mode does not expose Pro models in the ChatGPT model picker. ' +
89
+ if (modelStrategy === "select" &&
90
+ url &&
91
+ isTemporaryChatUrl(url) &&
92
+ /\bpro\b/i.test(desiredModel ?? "")) {
93
+ throw new Error("Temporary Chat mode does not expose Pro models in the ChatGPT model picker. " +
81
94
  'Remove "temporary-chat=true" from --chatgpt-url (or omit --chatgpt-url), or use a non-Pro model (e.g. --model gpt-5.2).');
82
95
  }
83
96
  return {
84
97
  chromeProfile: options.browserChromeProfile ?? DEFAULT_CHROME_PROFILE,
85
98
  chromePath: options.browserChromePath ?? null,
86
99
  chromeCookiePath: options.browserCookiePath ?? null,
100
+ attachRunning,
87
101
  url,
88
102
  debugPort: selectBrowserPort(options),
89
- timeoutMs: options.browserTimeout ? parseDuration(options.browserTimeout, DEFAULT_BROWSER_TIMEOUT_MS) : undefined,
103
+ timeoutMs: options.browserTimeout
104
+ ? parseDuration(options.browserTimeout, DEFAULT_BROWSER_TIMEOUT_MS)
105
+ : undefined,
90
106
  inputTimeoutMs: options.browserInputTimeout
91
107
  ? parseDuration(options.browserInputTimeout, DEFAULT_BROWSER_INPUT_TIMEOUT_MS)
92
108
  : undefined,
@@ -96,10 +112,13 @@ export async function buildBrowserConfig(options) {
96
112
  assistantRecheckTimeoutMs: options.browserRecheckTimeout
97
113
  ? parseDuration(options.browserRecheckTimeout, DEFAULT_BROWSER_RECHECK_TIMEOUT_MS)
98
114
  : undefined,
99
- reuseChromeWaitMs: options.browserReuseWait ? parseDuration(options.browserReuseWait, 0) : undefined,
115
+ reuseChromeWaitMs: options.browserReuseWait
116
+ ? parseDuration(options.browserReuseWait, 0)
117
+ : undefined,
100
118
  profileLockTimeoutMs: options.browserProfileLockTimeout
101
119
  ? parseDuration(options.browserProfileLockTimeout, 0)
102
120
  : undefined,
121
+ maxConcurrentTabs: parseMaxConcurrentTabs(options.browserMaxConcurrentTabs),
103
122
  autoReattachDelayMs: options.browserAutoReattachDelay
104
123
  ? parseDuration(options.browserAutoReattachDelay, 0)
105
124
  : undefined,
@@ -109,7 +128,9 @@ export async function buildBrowserConfig(options) {
109
128
  autoReattachTimeoutMs: options.browserAutoReattachTimeout
110
129
  ? parseDuration(options.browserAutoReattachTimeout, DEFAULT_BROWSER_AUTO_REATTACH_TIMEOUT_MS)
111
130
  : undefined,
112
- cookieSyncWaitMs: options.browserCookieWait ? parseDuration(options.browserCookieWait, 0) : undefined,
131
+ cookieSyncWaitMs: options.browserCookieWait
132
+ ? parseDuration(options.browserCookieWait, 0)
133
+ : undefined,
113
134
  cookieSync: options.browserNoCookieSync ? false : undefined,
114
135
  cookieNames,
115
136
  inlineCookies: inline?.cookies,
@@ -125,9 +146,33 @@ export async function buildBrowserConfig(options) {
125
146
  // Allow cookie failures by default so runs can continue without Chrome/Keychain secrets.
126
147
  allowCookieErrors: options.browserAllowCookieErrors ?? true,
127
148
  remoteChrome,
149
+ browserTabRef: options.browserTab ?? undefined,
128
150
  thinkingTime: options.browserThinkingTime,
151
+ researchMode: options.browserResearch === "deep" ? "deep" : "off",
152
+ archiveConversations: options.browserArchive,
129
153
  };
130
154
  }
155
+ function validateAttachRunningOptions(options, { attachRunning, hasInlineCookies, }) {
156
+ if (!attachRunning) {
157
+ return;
158
+ }
159
+ const conflicts = [
160
+ options.browserChromeProfile ? "--browser-chrome-profile" : null,
161
+ options.browserCookiePath ? "--browser-cookie-path" : null,
162
+ options.browserNoCookieSync ? "--browser-no-cookie-sync" : null,
163
+ options.browserHideWindow ? "--browser-hide-window" : null,
164
+ options.browserKeepBrowser ? "--browser-keep-browser" : null,
165
+ options.browserManualLogin ? "--browser-manual-login" : null,
166
+ options.browserManualLoginProfileDir ? "--browser-manual-login-profile-dir" : null,
167
+ hasInlineCookies ? "--browser-inline-cookies/--browser-inline-cookies-file" : null,
168
+ options.browserPort != null || options.browserDebugPort != null
169
+ ? "--browser-port/--browser-debug-port"
170
+ : null,
171
+ ].filter((value) => Boolean(value));
172
+ if (conflicts.length > 0) {
173
+ throw new Error(`--browser-attach-running cannot be combined with ${conflicts.join(", ")} because attach mode reuses an already-running browser instead of launching and configuring its own Chrome instance.`);
174
+ }
175
+ }
131
176
  function selectBrowserPort(options) {
132
177
  const candidate = options.browserPort ?? options.browserDebugPort;
133
178
  if (candidate === undefined || candidate === null)
@@ -137,6 +182,15 @@ function selectBrowserPort(options) {
137
182
  }
138
183
  return candidate;
139
184
  }
185
+ function parseMaxConcurrentTabs(raw) {
186
+ if (!raw)
187
+ return undefined;
188
+ const value = Number.parseInt(raw, 10);
189
+ if (!Number.isFinite(value) || value <= 0) {
190
+ throw new Error(`Invalid browser max concurrent tabs: ${raw}. Expected a positive integer.`);
191
+ }
192
+ return Math.trunc(value);
193
+ }
140
194
  export function mapModelToBrowserLabel(model) {
141
195
  const normalized = normalizeChatGptModelForBrowser(model);
142
196
  // Iterate ordered array to find first match (most specific first)
@@ -148,7 +202,7 @@ export function mapModelToBrowserLabel(model) {
148
202
  return DEFAULT_MODEL_TARGET;
149
203
  }
150
204
  export function resolveBrowserModelLabel(input, model) {
151
- const trimmed = input?.trim?.() ?? '';
205
+ const trimmed = input?.trim?.() ?? "";
152
206
  if (!trimmed) {
153
207
  return mapModelToBrowserLabel(model);
154
208
  }
@@ -161,7 +215,7 @@ export function resolveBrowserModelLabel(input, model) {
161
215
  function parseRemoteChromeTarget(raw) {
162
216
  const target = raw.trim();
163
217
  if (!target) {
164
- throw new Error('Invalid remote-chrome value: expected host:port but received an empty string.');
218
+ throw new Error("Invalid remote-chrome value: expected host:port but received an empty string.");
165
219
  }
166
220
  const ipv6Match = target.match(/^\[(.+)]:(\d+)$/);
167
221
  let host;
@@ -171,22 +225,22 @@ function parseRemoteChromeTarget(raw) {
171
225
  portSegment = ipv6Match[2]?.trim();
172
226
  }
173
227
  else {
174
- const lastColon = target.lastIndexOf(':');
228
+ const lastColon = target.lastIndexOf(":");
175
229
  if (lastColon === -1) {
176
230
  throw new Error(`Invalid remote-chrome format: ${target}. Expected host:port (IPv6 must use [host]:port notation).`);
177
231
  }
178
232
  host = target.slice(0, lastColon).trim();
179
233
  portSegment = target.slice(lastColon + 1).trim();
180
- if (host.includes(':')) {
234
+ if (host.includes(":")) {
181
235
  throw new Error(`Invalid remote-chrome format: ${target}. Wrap IPv6 addresses in brackets, e.g. --remote-chrome "[2001:db8::1]:9222".`);
182
236
  }
183
237
  }
184
238
  if (!host) {
185
239
  throw new Error(`Invalid remote-chrome format: ${target}. Host portion is missing; expected host:port.`);
186
240
  }
187
- const port = Number.parseInt(portSegment ?? '', 10);
241
+ const port = Number.parseInt(portSegment ?? "", 10);
188
242
  if (!Number.isFinite(port) || port <= 0 || port > 65_535) {
189
- throw new Error(`Invalid remote-chrome port: "${portSegment ?? ''}". Expected a number between 1 and 65535.`);
243
+ throw new Error(`Invalid remote-chrome port: "${portSegment ?? ""}". Expected a number between 1 and 65535.`);
190
244
  }
191
245
  return { host, port };
192
246
  }
@@ -194,7 +248,7 @@ function parseCookieNames(raw) {
194
248
  if (!raw)
195
249
  return undefined;
196
250
  const names = raw
197
- .split(',')
251
+ .split(",")
198
252
  .map((entry) => entry.trim())
199
253
  .filter(Boolean);
200
254
  return names.length ? names : undefined;
@@ -211,7 +265,7 @@ async function resolveInlineCookies({ inlineArg, inlineFileArg, envPayload, envF
211
265
  try {
212
266
  const stat = await fs.stat(resolved);
213
267
  if (stat.isFile()) {
214
- const fileContent = await fs.readFile(resolved, 'utf8');
268
+ const fileContent = await fs.readFile(resolved, "utf8");
215
269
  const parsed = parseInlineCookiesPayload(fileContent);
216
270
  if (parsed)
217
271
  return parsed;
@@ -224,10 +278,10 @@ async function resolveInlineCookies({ inlineArg, inlineFileArg, envPayload, envF
224
278
  return parseInlineCookiesPayload(trimmed);
225
279
  };
226
280
  const sources = [
227
- { value: inlineFileArg, allowPath: true, source: 'inline-file' },
228
- { value: inlineArg, allowPath: true, source: 'inline-arg' },
229
- { value: envFile, allowPath: true, source: 'env-file' },
230
- { value: envPayload, allowPath: false, source: 'env-payload' },
281
+ { value: inlineFileArg, allowPath: true, source: "inline-file" },
282
+ { value: inlineArg, allowPath: true, source: "inline-arg" },
283
+ { value: envFile, allowPath: true, source: "env-file" },
284
+ { value: envPayload, allowPath: false, source: "env-payload" },
231
285
  ];
232
286
  for (const { value, allowPath, source } of sources) {
233
287
  const parsed = await tryLoad(value, allowPath);
@@ -236,14 +290,14 @@ async function resolveInlineCookies({ inlineArg, inlineFileArg, envPayload, envF
236
290
  }
237
291
  // fallback: ~/.oracle/cookies.{json,base64}
238
292
  const oracleHome = getOracleHomeDir();
239
- const candidates = ['cookies.json', 'cookies.base64'];
293
+ const candidates = ["cookies.json", "cookies.base64"];
240
294
  for (const file of candidates) {
241
295
  const fullPath = path.join(oracleHome, file);
242
296
  try {
243
297
  const stat = await fs.stat(fullPath);
244
298
  if (!stat.isFile())
245
299
  continue;
246
- const content = await fs.readFile(fullPath, 'utf8');
300
+ const content = await fs.readFile(fullPath, "utf8");
247
301
  const parsed = parseInlineCookiesPayload(content);
248
302
  if (parsed)
249
303
  return { cookies: parsed, source: `home:${file}` };
@@ -263,8 +317,8 @@ function parseInlineCookiesPayload(raw) {
263
317
  let jsonPayload = text;
264
318
  // Attempt base64 decode first; fall back to raw text on failure.
265
319
  try {
266
- const decoded = Buffer.from(text, 'base64').toString('utf8');
267
- if (decoded.trim().startsWith('[')) {
320
+ const decoded = Buffer.from(text, "base64").toString("utf8");
321
+ if (decoded.trim().startsWith("[")) {
268
322
  jsonPayload = decoded;
269
323
  }
270
324
  }
@@ -1,81 +1,106 @@
1
- import { normalizeChatgptUrl, CHATGPT_URL } from '../browserMode.js';
1
+ import { normalizeChatgptUrl, CHATGPT_URL } from "../browserMode.js";
2
2
  export function applyBrowserDefaultsFromConfig(options, config, getSource) {
3
3
  const browser = config.browser;
4
4
  if (!browser)
5
5
  return;
6
6
  const isUnset = (key) => {
7
7
  const source = getSource(key);
8
- return source === undefined || source === 'default';
8
+ return source === undefined || source === "default";
9
9
  };
10
+ const attachRunningRequested = options.browserAttachRunning === true ||
11
+ (isUnset("browserAttachRunning") && browser.attachRunning === true);
10
12
  const configuredChatgptUrl = browser.chatgptUrl ?? browser.url;
11
13
  const cliChatgptSet = options.chatgptUrl !== undefined || options.browserUrl !== undefined;
12
- if (isUnset('chatgptUrl') && !cliChatgptSet && configuredChatgptUrl !== undefined) {
13
- options.chatgptUrl = normalizeChatgptUrl(configuredChatgptUrl ?? '', CHATGPT_URL);
14
+ if (isUnset("chatgptUrl") && !cliChatgptSet && configuredChatgptUrl !== undefined) {
15
+ options.chatgptUrl = normalizeChatgptUrl(configuredChatgptUrl ?? "", CHATGPT_URL);
14
16
  }
15
- if (isUnset('browserChromeProfile') && browser.chromeProfile !== undefined) {
17
+ if (!attachRunningRequested &&
18
+ isUnset("browserChromeProfile") &&
19
+ browser.chromeProfile !== undefined) {
16
20
  options.browserChromeProfile = browser.chromeProfile ?? undefined;
17
21
  }
18
- if (isUnset('browserChromePath') && browser.chromePath !== undefined) {
22
+ if (isUnset("browserChromePath") && browser.chromePath !== undefined) {
19
23
  options.browserChromePath = browser.chromePath ?? undefined;
20
24
  }
21
- if (isUnset('browserCookiePath') && browser.chromeCookiePath !== undefined) {
25
+ if (!attachRunningRequested &&
26
+ isUnset("browserCookiePath") &&
27
+ browser.chromeCookiePath !== undefined) {
22
28
  options.browserCookiePath = browser.chromeCookiePath ?? undefined;
23
29
  }
24
- if (isUnset('browserUrl') && options.browserUrl === undefined && browser.url !== undefined) {
30
+ if (isUnset("browserAttachRunning") && browser.attachRunning !== undefined) {
31
+ options.browserAttachRunning = browser.attachRunning;
32
+ }
33
+ if (isUnset("browserUrl") && options.browserUrl === undefined && browser.url !== undefined) {
25
34
  options.browserUrl = browser.url;
26
35
  }
27
- if (isUnset('browserTimeout') && typeof browser.timeoutMs === 'number') {
36
+ if (isUnset("browserTimeout") && typeof browser.timeoutMs === "number") {
28
37
  options.browserTimeout = String(browser.timeoutMs);
29
38
  }
30
- if (isUnset('browserPort') && typeof browser.debugPort === 'number') {
39
+ if (!attachRunningRequested && isUnset("browserPort") && typeof browser.debugPort === "number") {
31
40
  options.browserPort = browser.debugPort;
32
41
  }
33
- if (isUnset('browserInputTimeout') && typeof browser.inputTimeoutMs === 'number') {
42
+ if (isUnset("browserInputTimeout") && typeof browser.inputTimeoutMs === "number") {
34
43
  options.browserInputTimeout = String(browser.inputTimeoutMs);
35
44
  }
36
- if (isUnset('browserRecheckDelay') && typeof browser.assistantRecheckDelayMs === 'number') {
45
+ if (isUnset("browserRecheckDelay") && typeof browser.assistantRecheckDelayMs === "number") {
37
46
  options.browserRecheckDelay = String(browser.assistantRecheckDelayMs);
38
47
  }
39
- if (isUnset('browserRecheckTimeout') && typeof browser.assistantRecheckTimeoutMs === 'number') {
48
+ if (isUnset("browserRecheckTimeout") && typeof browser.assistantRecheckTimeoutMs === "number") {
40
49
  options.browserRecheckTimeout = String(browser.assistantRecheckTimeoutMs);
41
50
  }
42
- if (isUnset('browserReuseWait') && typeof browser.reuseChromeWaitMs === 'number') {
51
+ if (isUnset("browserReuseWait") && typeof browser.reuseChromeWaitMs === "number") {
43
52
  options.browserReuseWait = String(browser.reuseChromeWaitMs);
44
53
  }
45
- if (isUnset('browserProfileLockTimeout') && typeof browser.profileLockTimeoutMs === 'number') {
54
+ if (isUnset("browserProfileLockTimeout") && typeof browser.profileLockTimeoutMs === "number") {
46
55
  options.browserProfileLockTimeout = String(browser.profileLockTimeoutMs);
47
56
  }
48
- if (isUnset('browserAutoReattachDelay') && typeof browser.autoReattachDelayMs === 'number') {
57
+ if (isUnset("browserMaxConcurrentTabs") && typeof browser.maxConcurrentTabs === "number") {
58
+ options.browserMaxConcurrentTabs = String(browser.maxConcurrentTabs);
59
+ }
60
+ if (isUnset("browserAutoReattachDelay") && typeof browser.autoReattachDelayMs === "number") {
49
61
  options.browserAutoReattachDelay = String(browser.autoReattachDelayMs);
50
62
  }
51
- if (isUnset('browserAutoReattachInterval') && typeof browser.autoReattachIntervalMs === 'number') {
63
+ if (isUnset("browserAutoReattachInterval") &&
64
+ typeof browser.autoReattachIntervalMs === "number") {
52
65
  options.browserAutoReattachInterval = String(browser.autoReattachIntervalMs);
53
66
  }
54
- if (isUnset('browserAutoReattachTimeout') && typeof browser.autoReattachTimeoutMs === 'number') {
67
+ if (isUnset("browserAutoReattachTimeout") && typeof browser.autoReattachTimeoutMs === "number") {
55
68
  options.browserAutoReattachTimeout = String(browser.autoReattachTimeoutMs);
56
69
  }
57
- if (isUnset('browserCookieWait') && typeof browser.cookieSyncWaitMs === 'number') {
70
+ if (isUnset("browserCookieWait") && typeof browser.cookieSyncWaitMs === "number") {
58
71
  options.browserCookieWait = String(browser.cookieSyncWaitMs);
59
72
  }
60
- if (isUnset('browserHeadless') && browser.headless !== undefined) {
73
+ if (isUnset("browserHeadless") && browser.headless !== undefined) {
61
74
  options.browserHeadless = browser.headless;
62
75
  }
63
- if (isUnset('browserHideWindow') && browser.hideWindow !== undefined) {
76
+ if (!attachRunningRequested && isUnset("browserHideWindow") && browser.hideWindow !== undefined) {
64
77
  options.browserHideWindow = browser.hideWindow;
65
78
  }
66
- if (isUnset('browserKeepBrowser') && browser.keepBrowser !== undefined) {
79
+ if (!attachRunningRequested &&
80
+ isUnset("browserKeepBrowser") &&
81
+ browser.keepBrowser !== undefined) {
67
82
  options.browserKeepBrowser = browser.keepBrowser;
68
83
  }
69
- if (isUnset('browserModelStrategy') && browser.modelStrategy !== undefined) {
84
+ if (isUnset("browserModelStrategy") && browser.modelStrategy !== undefined) {
70
85
  options.browserModelStrategy = browser.modelStrategy;
71
86
  }
72
- if (isUnset('browserThinkingTime') && browser.thinkingTime !== undefined) {
87
+ if (isUnset("browserThinkingTime") && browser.thinkingTime !== undefined) {
73
88
  options.browserThinkingTime = browser.thinkingTime;
74
89
  }
75
- if (isUnset('browserManualLogin') && browser.manualLogin !== undefined) {
90
+ if (isUnset("browserResearch") && browser.researchMode !== undefined) {
91
+ options.browserResearch = browser.researchMode;
92
+ }
93
+ if (isUnset("browserArchive") && browser.archiveConversations !== undefined) {
94
+ options.browserArchive = browser.archiveConversations;
95
+ }
96
+ if (!attachRunningRequested &&
97
+ isUnset("browserManualLogin") &&
98
+ browser.manualLogin !== undefined) {
76
99
  options.browserManualLogin = browser.manualLogin;
77
100
  }
78
- if (isUnset('browserManualLoginProfileDir') && browser.manualLoginProfileDir !== undefined) {
101
+ if (!attachRunningRequested &&
102
+ isUnset("browserManualLoginProfileDir") &&
103
+ browser.manualLoginProfileDir !== undefined) {
79
104
  options.browserManualLoginProfileDir = browser.manualLoginProfileDir;
80
105
  }
81
106
  }