@steipete/oracle 0.8.4 → 0.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -1
- package/dist/bin/oracle-cli.js +291 -16
- package/dist/scripts/debug/extract-chatgpt-response.js +53 -0
- package/dist/src/bridge/connection.js +103 -0
- package/dist/src/bridge/userConfigFile.js +28 -0
- package/dist/src/browser/actions/assistantResponse.js +85 -42
- package/dist/src/browser/actions/promptComposer.js +141 -32
- package/dist/src/browser/chromeLifecycle.js +78 -9
- package/dist/src/browser/config.js +14 -0
- package/dist/src/browser/detect.js +164 -0
- package/dist/src/browser/index.js +394 -24
- package/dist/src/browser/profileState.js +93 -0
- package/dist/src/cli/bridge/claudeConfig.js +54 -0
- package/dist/src/cli/bridge/client.js +73 -0
- package/dist/src/cli/bridge/codexConfig.js +43 -0
- package/dist/src/cli/bridge/doctor.js +107 -0
- package/dist/src/cli/bridge/host.js +259 -0
- package/dist/src/cli/browserConfig.js +21 -0
- package/dist/src/cli/browserDefaults.js +21 -0
- package/dist/src/cli/engine.js +17 -1
- package/dist/src/cli/options.js +14 -0
- package/dist/src/cli/runOptions.js +4 -0
- package/dist/src/cli/sessionRunner.js +149 -0
- package/dist/src/cli/tui/index.js +1 -0
- package/dist/src/mcp/tools/consult.js +81 -15
- package/dist/src/mcp/tools/sessions.js +15 -6
- package/dist/src/mcp/types.js +4 -0
- package/dist/src/mcp/utils.js +12 -2
- package/dist/src/oracle/background.js +1 -2
- package/dist/src/oracle/client.js +5 -2
- package/dist/src/oracle/files.js +2 -2
- package/dist/src/oracle/modelResolver.js +33 -1
- package/dist/src/oracle/run.js +1 -0
- package/dist/src/remote/client.js +6 -5
- package/dist/src/remote/health.js +113 -0
- package/dist/src/remote/remoteServiceConfig.js +31 -0
- package/dist/src/remote/server.js +28 -1
- package/dist/src/sessionManager.js +72 -7
- package/dist/src/sessionStore.js +2 -2
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/package.json +21 -21
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
package/README.md
CHANGED
|
@@ -42,6 +42,7 @@ npx -y @steipete/oracle --engine browser --model gemini-3-pro --prompt "a cute r
|
|
|
42
42
|
# Sessions (list and replay)
|
|
43
43
|
npx -y @steipete/oracle status --hours 72
|
|
44
44
|
npx -y @steipete/oracle session <id> --render
|
|
45
|
+
npx -y @steipete/oracle restart <id>
|
|
45
46
|
|
|
46
47
|
# TUI (interactive, only for humans)
|
|
47
48
|
npx -y @steipete/oracle tui
|
|
@@ -100,6 +101,24 @@ npx -y @steipete/oracle oracle-mcp
|
|
|
100
101
|
- Sessions you can replay (`oracle status`, `oracle session <id> --render`).
|
|
101
102
|
- Session logs and bundles live in `~/.oracle/sessions` (override with `ORACLE_HOME_DIR`).
|
|
102
103
|
|
|
104
|
+
## Browser auto-reattach (long Pro runs)
|
|
105
|
+
|
|
106
|
+
When browser runs time out (common with long GPT‑5.x Pro responses), Oracle can keep polling the existing ChatGPT tab and capture the final answer without manual `oracle session <id>` commands.
|
|
107
|
+
|
|
108
|
+
Enable auto-reattach by setting a non-zero interval:
|
|
109
|
+
- `--browser-auto-reattach-delay` — wait before the first retry (e.g. `30s`)
|
|
110
|
+
- `--browser-auto-reattach-interval` — how often to retry (e.g. `2m`)
|
|
111
|
+
- `--browser-auto-reattach-timeout` — per-attempt budget (default `2m`)
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
oracle --engine browser \
|
|
115
|
+
--browser-timeout 6m \
|
|
116
|
+
--browser-auto-reattach-delay 30s \
|
|
117
|
+
--browser-auto-reattach-interval 2m \
|
|
118
|
+
--browser-auto-reattach-timeout 2m \
|
|
119
|
+
-p "Run the long UI audit" --file "src/**/*.ts"
|
|
120
|
+
```
|
|
121
|
+
|
|
103
122
|
## Flags you’ll actually use
|
|
104
123
|
|
|
105
124
|
| Flag | Purpose |
|
|
@@ -117,8 +136,16 @@ npx -y @steipete/oracle oracle-mcp
|
|
|
117
136
|
| `--browser-port <port>` | Pin the Chrome DevTools port (WSL/Windows firewall helper). |
|
|
118
137
|
| `--browser-inline-cookies[(-file)] <payload|path>` | Supply cookies without Chrome/Keychain (browser). |
|
|
119
138
|
| `--browser-timeout`, `--browser-input-timeout` | Control overall/browser input timeouts (supports h/m/s/ms). |
|
|
139
|
+
| `--browser-recheck-delay`, `--browser-recheck-timeout` | Delayed recheck for long Pro runs: wait then retry capture after timeout (supports h/m/s/ms). |
|
|
140
|
+
| `--browser-reuse-wait` | Wait for a shared Chrome profile before launching (parallel browser runs). |
|
|
141
|
+
| `--browser-profile-lock-timeout` | Wait for the shared manual-login profile lock before sending (serializes parallel runs). |
|
|
120
142
|
| `--render`, `--copy` | Print and/or copy the assembled markdown bundle. |
|
|
121
143
|
| `--wait` | Block for background API runs (e.g., GPT‑5.1 Pro) instead of detaching. |
|
|
144
|
+
| `--timeout <seconds\|auto>` | Overall API deadline (auto = 60m for pro, 120s otherwise). |
|
|
145
|
+
| `--background`, `--no-background` | Force Responses API background mode (create + retrieve) for API runs. |
|
|
146
|
+
| `--http-timeout <ms\|s\|m\|h>` | HTTP client timeout (default 20m). |
|
|
147
|
+
| `--zombie-timeout <ms\|s\|m\|h>` | Override stale-session cutoff used by `oracle status`. |
|
|
148
|
+
| `--zombie-last-activity` | Use last log activity to detect stale sessions. |
|
|
122
149
|
| `--write-output <path>` | Save only the final answer (multi-model adds `.<model>`). |
|
|
123
150
|
| `--files-report` | Print per-file token usage. |
|
|
124
151
|
| `--dry-run [summary\|json\|full]` | Preview without sending. |
|
|
@@ -149,7 +176,8 @@ Advanced flags
|
|
|
149
176
|
|
|
150
177
|
| Area | Flags |
|
|
151
178
|
| --- | --- |
|
|
152
|
-
| Browser | `--browser-manual-login`, `--browser-thinking-time`, `--browser-timeout`, `--browser-input-timeout`, `--browser-cookie-wait`, `--browser-inline-cookies[(-file)]`, `--browser-attachments`, `--browser-inline-files`, `--browser-bundle-files`, `--browser-keep-browser`, `--browser-headless`, `--browser-hide-window`, `--browser-no-cookie-sync`, `--browser-allow-cookie-errors`, `--browser-chrome-path`, `--browser-cookie-path`, `--chatgpt-url` |
|
|
179
|
+
| Browser | `--browser-manual-login`, `--browser-thinking-time`, `--browser-timeout`, `--browser-input-timeout`, `--browser-recheck-delay`, `--browser-recheck-timeout`, `--browser-reuse-wait`, `--browser-profile-lock-timeout`, `--browser-auto-reattach-delay`, `--browser-auto-reattach-interval`, `--browser-auto-reattach-timeout`, `--browser-cookie-wait`, `--browser-inline-cookies[(-file)]`, `--browser-attachments`, `--browser-inline-files`, `--browser-bundle-files`, `--browser-keep-browser`, `--browser-headless`, `--browser-hide-window`, `--browser-no-cookie-sync`, `--browser-allow-cookie-errors`, `--browser-chrome-path`, `--browser-cookie-path`, `--chatgpt-url` |
|
|
180
|
+
| Run control | `--background`, `--no-background`, `--http-timeout`, `--zombie-timeout`, `--zombie-last-activity` |
|
|
153
181
|
| Azure/OpenAI | `--azure-endpoint`, `--azure-deployment`, `--azure-api-version`, `--base-url` |
|
|
154
182
|
|
|
155
183
|
Remote browser example
|
|
@@ -171,6 +199,7 @@ oracle status --clear --hours 168
|
|
|
171
199
|
```
|
|
172
200
|
|
|
173
201
|
## More docs
|
|
202
|
+
- Bridge (Windows host → Linux client): [docs/bridge.md](docs/bridge.md)
|
|
174
203
|
- Browser mode & forks: [docs/browser-mode.md](docs/browser-mode.md) (includes `oracle serve` remote service), [docs/chromium-forks.md](docs/chromium-forks.md), [docs/linux.md](docs/linux.md)
|
|
175
204
|
- MCP: [docs/mcp.md](docs/mcp.md)
|
|
176
205
|
- OpenAI/Azure/OpenRouter endpoints: [docs/openai-endpoints.md](docs/openai-endpoints.md), [docs/openrouter.md](docs/openrouter.md)
|
package/dist/bin/oracle-cli.js
CHANGED
|
@@ -20,7 +20,7 @@ import { CHATGPT_URL } from '../src/browserMode.js';
|
|
|
20
20
|
import { createRemoteBrowserExecutor } from '../src/remote/client.js';
|
|
21
21
|
import { createGeminiWebExecutor } from '../src/gemini-web/index.js';
|
|
22
22
|
import { applyHelpStyling } from '../src/cli/help.js';
|
|
23
|
-
import { collectPaths, collectModelList, parseFloatOption, parseIntOption, parseSearchOption, usesDefaultStatusFilters, resolvePreviewMode, normalizeModelOption, normalizeBaseUrl, resolveApiModel, inferModelFromLabel, parseHeartbeatOption, parseTimeoutOption, mergePathLikeOptions, dedupePathInputs, } from '../src/cli/options.js';
|
|
23
|
+
import { collectPaths, collectModelList, parseFloatOption, parseIntOption, parseSearchOption, usesDefaultStatusFilters, resolvePreviewMode, normalizeModelOption, normalizeBaseUrl, resolveApiModel, inferModelFromLabel, parseHeartbeatOption, parseTimeoutOption, parseDurationOption, mergePathLikeOptions, dedupePathInputs, } from '../src/cli/options.js';
|
|
24
24
|
import { copyToClipboard } from '../src/cli/clipboard.js';
|
|
25
25
|
import { buildMarkdownBundle } from '../src/cli/markdownBundle.js';
|
|
26
26
|
import { shouldDetachSession } from '../src/cli/detach.js';
|
|
@@ -46,9 +46,20 @@ import { resolveNotificationSettings, deriveNotificationSettingsFromMetadata, }
|
|
|
46
46
|
import { loadUserConfig } from '../src/config.js';
|
|
47
47
|
import { applyBrowserDefaultsFromConfig } from '../src/cli/browserDefaults.js';
|
|
48
48
|
import { shouldBlockDuplicatePrompt } from '../src/cli/duplicatePromptGuard.js';
|
|
49
|
+
import { resolveRemoteServiceConfig } from '../src/remote/remoteServiceConfig.js';
|
|
49
50
|
const VERSION = getCliVersion();
|
|
50
51
|
const CLI_ENTRYPOINT = fileURLToPath(import.meta.url);
|
|
51
|
-
const
|
|
52
|
+
const LEGACY_FLAG_ALIASES = new Map([
|
|
53
|
+
['--[no-]notify', '--notify'],
|
|
54
|
+
['--[no-]notify-sound', '--notify-sound'],
|
|
55
|
+
['--[no-]background', '--background'],
|
|
56
|
+
]);
|
|
57
|
+
const normalizedArgv = process.argv.map((arg, index) => {
|
|
58
|
+
if (index < 2)
|
|
59
|
+
return arg;
|
|
60
|
+
return LEGACY_FLAG_ALIASES.get(arg) ?? arg;
|
|
61
|
+
});
|
|
62
|
+
const rawCliArgs = normalizedArgv.slice(2);
|
|
52
63
|
const userCliArgs = rawCliArgs[0] === CLI_ENTRYPOINT ? rawCliArgs.slice(1) : rawCliArgs;
|
|
53
64
|
const isTty = process.stdout.isTTY;
|
|
54
65
|
const program = new Command();
|
|
@@ -120,12 +131,22 @@ program
|
|
|
120
131
|
.addOption(new Option('--mode <mode>', 'Alias for --engine (api | browser).').choices(['api', 'browser']).hideHelp())
|
|
121
132
|
.option('--files-report', 'Show token usage per attached file (also prints automatically when files exceed the token budget).', false)
|
|
122
133
|
.option('-v, --verbose', 'Enable verbose logging for all operations.', false)
|
|
123
|
-
.addOption(new Option('--
|
|
124
|
-
.default(undefined))
|
|
125
|
-
.addOption(new Option('--
|
|
134
|
+
.addOption(new Option('--notify', 'Desktop notification when a session finishes (default on unless CI/SSH).').default(undefined))
|
|
135
|
+
.addOption(new Option('--no-notify', 'Disable desktop notifications.').default(undefined))
|
|
136
|
+
.addOption(new Option('--notify-sound', 'Play a notification sound on completion (default off).').default(undefined))
|
|
137
|
+
.addOption(new Option('--no-notify-sound', 'Disable notification sounds.').default(undefined))
|
|
126
138
|
.addOption(new Option('--timeout <seconds|auto>', 'Overall timeout before aborting the API call (auto = 60m for gpt-5.2-pro, 120s otherwise).')
|
|
127
139
|
.argParser(parseTimeoutOption)
|
|
128
140
|
.default('auto'))
|
|
141
|
+
.addOption(new Option('--background', 'Use Responses API background mode (create + retrieve) for API runs.').default(undefined))
|
|
142
|
+
.addOption(new Option('--no-background', 'Disable Responses API background mode.').default(undefined))
|
|
143
|
+
.addOption(new Option('--http-timeout <ms|s|m|h>', 'HTTP client timeout for API requests (default 20m).')
|
|
144
|
+
.argParser((value) => parseDurationOption(value, 'HTTP timeout'))
|
|
145
|
+
.default(undefined))
|
|
146
|
+
.addOption(new Option('--zombie-timeout <ms|s|m|h>', 'Override stale-session cutoff used by `oracle status` (default 60m).')
|
|
147
|
+
.argParser((value) => parseDurationOption(value, 'Zombie timeout'))
|
|
148
|
+
.default(undefined))
|
|
149
|
+
.option('--zombie-last-activity', 'Base stale-session detection on last log activity instead of start time.', false)
|
|
129
150
|
.addOption(new Option('--preview [mode]', '(alias) Preview the request without calling the model (summary | json | full). Deprecated: use --dry-run instead.')
|
|
130
151
|
.hideHelp()
|
|
131
152
|
.choices(['summary', 'json', 'full'])
|
|
@@ -162,7 +183,14 @@ program
|
|
|
162
183
|
.addOption(new Option('--chatgpt-url <url>', `Override the ChatGPT web URL (e.g., workspace/folder like https://chatgpt.com/g/.../project; default ${CHATGPT_URL}).`))
|
|
163
184
|
.addOption(new Option('--browser-url <url>', `Alias for --chatgpt-url (default ${CHATGPT_URL}).`).hideHelp())
|
|
164
185
|
.addOption(new Option('--browser-timeout <ms|s|m>', 'Maximum time to wait for an answer (default 1200s / 20m).').hideHelp())
|
|
165
|
-
.addOption(new Option('--browser-input-timeout <ms|s|m>', 'Maximum time to wait for the prompt textarea (default
|
|
186
|
+
.addOption(new Option('--browser-input-timeout <ms|s|m>', 'Maximum time to wait for the prompt textarea (default 60s).').hideHelp())
|
|
187
|
+
.addOption(new Option('--browser-recheck-delay <ms|s|m|h>', 'After an assistant timeout, wait this long then revisit the conversation to retry capture.').hideHelp())
|
|
188
|
+
.addOption(new Option('--browser-recheck-timeout <ms|s|m|h>', 'Time budget for the delayed recheck attempt (default 120s).').hideHelp())
|
|
189
|
+
.addOption(new Option('--browser-reuse-wait <ms|s|m|h>', 'Wait for a shared Chrome profile to appear before launching a new one (helps parallel runs).').hideHelp())
|
|
190
|
+
.addOption(new Option('--browser-profile-lock-timeout <ms|s|m|h>', 'Wait for the shared manual-login profile lock before sending (serializes parallel runs).').hideHelp())
|
|
191
|
+
.addOption(new Option('--browser-auto-reattach-delay <ms|s|m|h>', 'Delay before starting periodic auto-reattach attempts after a timeout.').hideHelp())
|
|
192
|
+
.addOption(new Option('--browser-auto-reattach-interval <ms|s|m|h>', 'Interval between auto-reattach attempts (0 disables).').hideHelp())
|
|
193
|
+
.addOption(new Option('--browser-auto-reattach-timeout <ms|s|m|h>', 'Time budget for each auto-reattach attempt (default 120s).').hideHelp())
|
|
166
194
|
.addOption(new Option('--browser-cookie-wait <ms|s|m>', 'Wait before retrying cookie sync when Chrome cookies are empty or locked.').hideHelp())
|
|
167
195
|
.addOption(new Option('--browser-port <port>', 'Use a fixed Chrome DevTools port (helpful on WSL firewalls).')
|
|
168
196
|
.argParser(parseIntOption))
|
|
@@ -219,14 +247,73 @@ program
|
|
|
219
247
|
.option('--host <address>', 'Interface to bind (default 0.0.0.0).')
|
|
220
248
|
.option('--port <number>', 'Port to listen on (default random).', parseIntOption)
|
|
221
249
|
.option('--token <value>', 'Access token clients must provide (random if omitted).')
|
|
250
|
+
.option('--manual-login', 'Use a dedicated Chrome profile for manual login (recommended when cookie sync is unavailable).', false)
|
|
251
|
+
.option('--manual-login-profile-dir <path>', 'Chrome profile directory for manual login (default ~/.oracle/browser-profile).')
|
|
222
252
|
.action(async (commandOptions) => {
|
|
223
253
|
const { serveRemote } = await import('../src/remote/server.js');
|
|
224
254
|
await serveRemote({
|
|
225
255
|
host: commandOptions.host,
|
|
226
256
|
port: commandOptions.port,
|
|
227
257
|
token: commandOptions.token,
|
|
258
|
+
manualLoginDefault: commandOptions.manualLogin,
|
|
259
|
+
manualLoginProfileDir: commandOptions.manualLoginProfileDir,
|
|
228
260
|
});
|
|
229
261
|
});
|
|
262
|
+
const bridgeCommand = program.command('bridge').description('Bridge a Windows-hosted ChatGPT session to Linux clients.');
|
|
263
|
+
bridgeCommand
|
|
264
|
+
.command('host')
|
|
265
|
+
.description('Start a secure oracle serve host (optionally with an SSH reverse tunnel).')
|
|
266
|
+
.option('--bind <host:port>', 'Local bind address for the host service (default 127.0.0.1:9473).')
|
|
267
|
+
.option('--token <token|auto>', 'Service access token (default auto).', 'auto')
|
|
268
|
+
.option('--write-connection <path>', 'Write a connection artifact JSON (default ~/.oracle/bridge-connection.json).')
|
|
269
|
+
.option('--ssh <user@host>', 'Maintain an SSH reverse tunnel to the Linux host (ssh -N -R ...).')
|
|
270
|
+
.option('--ssh-remote-port <port>', 'Remote port to bind on the Linux host (default matches --bind port).', parseIntOption)
|
|
271
|
+
.option('--ssh-identity <path>', 'SSH identity file (ssh -i).')
|
|
272
|
+
.option('--ssh-extra-args <args>', 'Extra args passed to ssh (quoted string).')
|
|
273
|
+
.option('--background', 'Run the host in the background and write pid/log files.', false)
|
|
274
|
+
.option('--foreground', 'Run the host in the foreground (default).', false)
|
|
275
|
+
.option('--print', 'Print the client connection string (includes token).', false)
|
|
276
|
+
.option('--print-token', 'Print only the token.', false)
|
|
277
|
+
.action(async (commandOptions) => {
|
|
278
|
+
const { runBridgeHost } = await import('../src/cli/bridge/host.js');
|
|
279
|
+
await runBridgeHost(commandOptions);
|
|
280
|
+
});
|
|
281
|
+
bridgeCommand
|
|
282
|
+
.command('client')
|
|
283
|
+
.description('Configure this machine to use a remote oracle serve host.')
|
|
284
|
+
.requiredOption('--connect <connection>', 'Connection string or path to bridge-connection.json.')
|
|
285
|
+
.option('--config <path>', 'Override the oracle config file location (default ~/.oracle/config.json).')
|
|
286
|
+
.option('--no-write-config', 'Do not write ~/.oracle/config.json (just validate).')
|
|
287
|
+
.option('--no-test', 'Skip remote /health check.')
|
|
288
|
+
.option('--print-env', 'Print env var exports (includes token).', false)
|
|
289
|
+
.action(async (commandOptions) => {
|
|
290
|
+
const { runBridgeClient } = await import('../src/cli/bridge/client.js');
|
|
291
|
+
await runBridgeClient(commandOptions);
|
|
292
|
+
});
|
|
293
|
+
bridgeCommand
|
|
294
|
+
.command('doctor')
|
|
295
|
+
.description('Diagnose bridge connectivity and browser engine prerequisites.')
|
|
296
|
+
.option('--verbose', 'Show extra diagnostics.', false)
|
|
297
|
+
.action(async (commandOptions) => {
|
|
298
|
+
const { runBridgeDoctor } = await import('../src/cli/bridge/doctor.js');
|
|
299
|
+
await runBridgeDoctor(commandOptions);
|
|
300
|
+
});
|
|
301
|
+
bridgeCommand
|
|
302
|
+
.command('codex-config')
|
|
303
|
+
.description('Print a Codex CLI MCP server config snippet for oracle-mcp.')
|
|
304
|
+
.option('--print-token', 'Include ORACLE_REMOTE_TOKEN in the snippet.', false)
|
|
305
|
+
.action(async (commandOptions) => {
|
|
306
|
+
const { runBridgeCodexConfig } = await import('../src/cli/bridge/codexConfig.js');
|
|
307
|
+
await runBridgeCodexConfig(commandOptions);
|
|
308
|
+
});
|
|
309
|
+
bridgeCommand
|
|
310
|
+
.command('claude-config')
|
|
311
|
+
.description('Print a Claude Code MCP config snippet (.mcp.json) for oracle-mcp.')
|
|
312
|
+
.option('--print-token', 'Include ORACLE_REMOTE_TOKEN in the snippet.', false)
|
|
313
|
+
.action(async (commandOptions) => {
|
|
314
|
+
const { runBridgeClaudeConfig } = await import('../src/cli/bridge/claudeConfig.js');
|
|
315
|
+
await runBridgeClaudeConfig(commandOptions);
|
|
316
|
+
});
|
|
230
317
|
program
|
|
231
318
|
.command('tui')
|
|
232
319
|
.description('Launch the interactive terminal UI for humans (no automation).')
|
|
@@ -299,6 +386,17 @@ const statusCommand = program
|
|
|
299
386
|
showExamples,
|
|
300
387
|
});
|
|
301
388
|
});
|
|
389
|
+
program
|
|
390
|
+
.command('restart <id>')
|
|
391
|
+
.description('Re-run a stored session as a new session (clones options).')
|
|
392
|
+
.addOption(new Option('--wait').default(undefined))
|
|
393
|
+
.addOption(new Option('--no-wait').default(undefined).hideHelp())
|
|
394
|
+
.option('--remote-host <host:port>', 'Delegate browser runs to a remote `oracle serve` instance.')
|
|
395
|
+
.option('--remote-token <token>', 'Access token for the remote `oracle serve` instance.')
|
|
396
|
+
.action(async (sessionId, _options, cmd) => {
|
|
397
|
+
const restartOptions = cmd.opts();
|
|
398
|
+
await restartSession(sessionId, restartOptions);
|
|
399
|
+
});
|
|
302
400
|
function buildRunOptions(options, overrides = {}) {
|
|
303
401
|
if (!options.prompt) {
|
|
304
402
|
throw new Error('Prompt is required.');
|
|
@@ -323,6 +421,9 @@ function buildRunOptions(options, overrides = {}) {
|
|
|
323
421
|
maxOutput: overrides.maxOutput ?? options.maxOutput,
|
|
324
422
|
system: overrides.system ?? options.system,
|
|
325
423
|
timeoutSeconds: overrides.timeoutSeconds ?? options.timeout,
|
|
424
|
+
httpTimeoutMs: overrides.httpTimeoutMs ?? options.httpTimeout,
|
|
425
|
+
zombieTimeoutMs: overrides.zombieTimeoutMs ?? options.zombieTimeout,
|
|
426
|
+
zombieUseLastActivity: overrides.zombieUseLastActivity ?? options.zombieLastActivity,
|
|
326
427
|
silent: overrides.silent ?? options.silent,
|
|
327
428
|
search: overrides.search ?? options.search,
|
|
328
429
|
preview: overrides.preview ?? undefined,
|
|
@@ -373,6 +474,10 @@ function buildRunOptionsFromMetadata(metadata) {
|
|
|
373
474
|
apiKey: undefined,
|
|
374
475
|
baseUrl: normalizeBaseUrl(stored.baseUrl),
|
|
375
476
|
azure: stored.azure,
|
|
477
|
+
timeoutSeconds: stored.timeoutSeconds,
|
|
478
|
+
httpTimeoutMs: stored.httpTimeoutMs,
|
|
479
|
+
zombieTimeoutMs: stored.zombieTimeoutMs,
|
|
480
|
+
zombieUseLastActivity: stored.zombieUseLastActivity,
|
|
376
481
|
sessionId: metadata.id,
|
|
377
482
|
verbose: stored.verbose,
|
|
378
483
|
heartbeatIntervalMs: stored.heartbeatIntervalMs,
|
|
@@ -446,8 +551,14 @@ async function runRootCommand(options) {
|
|
|
446
551
|
}
|
|
447
552
|
};
|
|
448
553
|
applyRetentionOption();
|
|
449
|
-
const
|
|
450
|
-
|
|
554
|
+
const remoteConfig = resolveRemoteServiceConfig({
|
|
555
|
+
cliHost: options.remoteHost,
|
|
556
|
+
cliToken: options.remoteToken,
|
|
557
|
+
userConfig,
|
|
558
|
+
env: process.env,
|
|
559
|
+
});
|
|
560
|
+
const remoteHost = remoteConfig.host;
|
|
561
|
+
const remoteToken = remoteConfig.token;
|
|
451
562
|
if (remoteHost) {
|
|
452
563
|
console.log(chalk.dim(`Remote browser host detected: ${remoteHost}`));
|
|
453
564
|
}
|
|
@@ -572,11 +683,10 @@ async function runRootCommand(options) {
|
|
|
572
683
|
// - otherwise block for fast models (gpt-5.1, browser) and detach by default for pro API runs
|
|
573
684
|
let waitPreference = resolveWaitFlag({
|
|
574
685
|
waitFlag: options.wait,
|
|
575
|
-
noWaitFlag: options.noWait,
|
|
576
686
|
model: resolvedModel,
|
|
577
687
|
engine,
|
|
578
688
|
});
|
|
579
|
-
if (remoteHost &&
|
|
689
|
+
if (remoteHost && waitPreference === false) {
|
|
580
690
|
console.log(chalk.dim('Remote browser runs require --wait; ignoring --no-wait.'));
|
|
581
691
|
waitPreference = true;
|
|
582
692
|
}
|
|
@@ -752,7 +862,7 @@ async function runRootCommand(options) {
|
|
|
752
862
|
const baseRunOptions = buildRunOptions(resolvedOptions, {
|
|
753
863
|
preview: false,
|
|
754
864
|
previewMode: undefined,
|
|
755
|
-
background:
|
|
865
|
+
background: resolvedOptions.background ?? userConfig.background,
|
|
756
866
|
baseUrl: resolvedBaseUrl,
|
|
757
867
|
});
|
|
758
868
|
enforceBrowserSearchFlag(baseRunOptions, sessionMode, console.log);
|
|
@@ -764,6 +874,13 @@ async function runRootCommand(options) {
|
|
|
764
874
|
...baseRunOptions,
|
|
765
875
|
mode: sessionMode,
|
|
766
876
|
browserConfig,
|
|
877
|
+
waitPreference,
|
|
878
|
+
youtube: options.youtube,
|
|
879
|
+
generateImage: options.generateImage,
|
|
880
|
+
editImage: options.editImage,
|
|
881
|
+
outputPath: options.output,
|
|
882
|
+
aspectRatio: options.aspect,
|
|
883
|
+
geminiShowThoughts: options.geminiShowThoughts,
|
|
767
884
|
}, process.cwd(), notifications);
|
|
768
885
|
const liveRunOptions = {
|
|
769
886
|
...baseRunOptions,
|
|
@@ -805,7 +922,7 @@ async function runRootCommand(options) {
|
|
|
805
922
|
await attachSession(sessionMeta.id, { suppressMetadata: true });
|
|
806
923
|
}
|
|
807
924
|
}
|
|
808
|
-
async function runInteractiveSession(sessionMeta, runOptions, mode, browserConfig, showReattachHint = true, notifications, userConfig, suppressSummary = false, browserDeps) {
|
|
925
|
+
async function runInteractiveSession(sessionMeta, runOptions, mode, browserConfig, showReattachHint = true, notifications, userConfig, suppressSummary = false, browserDeps, cwd = process.cwd()) {
|
|
809
926
|
const { logLine, writeChunk, stream } = sessionStore.createLogWriter(sessionMeta.id);
|
|
810
927
|
let headerAugmented = false;
|
|
811
928
|
const combinedLog = (message = '') => {
|
|
@@ -834,7 +951,7 @@ async function runInteractiveSession(sessionMeta, runOptions, mode, browserConfi
|
|
|
834
951
|
runOptions,
|
|
835
952
|
mode,
|
|
836
953
|
browserConfig,
|
|
837
|
-
cwd
|
|
954
|
+
cwd,
|
|
838
955
|
log: combinedLog,
|
|
839
956
|
write: combinedWrite,
|
|
840
957
|
version: VERSION,
|
|
@@ -877,6 +994,140 @@ async function launchDetachedSession(sessionId) {
|
|
|
877
994
|
}
|
|
878
995
|
});
|
|
879
996
|
}
|
|
997
|
+
async function restartSession(sessionId, options) {
|
|
998
|
+
const metadata = await sessionStore.readSession(sessionId);
|
|
999
|
+
if (!metadata) {
|
|
1000
|
+
console.error(chalk.red(`No session found with ID ${sessionId}`));
|
|
1001
|
+
process.exitCode = 1;
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
const runOptions = buildRunOptionsFromMetadata(metadata);
|
|
1005
|
+
if (!runOptions.prompt) {
|
|
1006
|
+
console.error(chalk.red(`Session ${sessionId} has no stored prompt; cannot restart.`));
|
|
1007
|
+
process.exitCode = 1;
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
const sessionMode = getSessionMode(metadata);
|
|
1011
|
+
const engine = sessionMode === 'browser' ? 'browser' : 'api';
|
|
1012
|
+
const browserConfig = getBrowserConfigFromMetadata(metadata);
|
|
1013
|
+
if (sessionMode === 'browser' && !browserConfig) {
|
|
1014
|
+
console.error(chalk.red(`Session ${sessionId} is missing browser config; cannot restart.`));
|
|
1015
|
+
process.exitCode = 1;
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
const userConfig = (await loadUserConfig()).config;
|
|
1019
|
+
const cwd = metadata.cwd ?? process.cwd();
|
|
1020
|
+
const storedOptions = metadata.options ?? {};
|
|
1021
|
+
if (runOptions.file && runOptions.file.length > 0) {
|
|
1022
|
+
const isBrowserMode = engine === 'browser';
|
|
1023
|
+
const filesToValidate = isBrowserMode ? runOptions.file.filter((f) => !isMediaFile(f)) : runOptions.file;
|
|
1024
|
+
if (filesToValidate.length > 0) {
|
|
1025
|
+
await readFiles(filesToValidate, { cwd });
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
enforceBrowserSearchFlag(runOptions, sessionMode, console.log);
|
|
1029
|
+
let waitPreference = resolveRestartWaitPreference({
|
|
1030
|
+
waitFlag: options.wait,
|
|
1031
|
+
storedPreference: storedOptions.waitPreference,
|
|
1032
|
+
model: runOptions.model,
|
|
1033
|
+
engine,
|
|
1034
|
+
});
|
|
1035
|
+
const remoteConfig = resolveRemoteServiceConfig({
|
|
1036
|
+
cliHost: options.remoteHost,
|
|
1037
|
+
cliToken: options.remoteToken,
|
|
1038
|
+
userConfig,
|
|
1039
|
+
env: process.env,
|
|
1040
|
+
});
|
|
1041
|
+
const remoteHost = remoteConfig.host;
|
|
1042
|
+
const remoteToken = remoteConfig.token;
|
|
1043
|
+
if (remoteHost && engine !== 'browser') {
|
|
1044
|
+
throw new Error('--remote-host requires a browser session.');
|
|
1045
|
+
}
|
|
1046
|
+
if (remoteHost) {
|
|
1047
|
+
console.log(chalk.dim(`Remote browser host detected: ${remoteHost}`));
|
|
1048
|
+
}
|
|
1049
|
+
if (remoteHost && waitPreference === false) {
|
|
1050
|
+
console.log(chalk.dim('Remote browser runs require --wait; ignoring --no-wait.'));
|
|
1051
|
+
waitPreference = true;
|
|
1052
|
+
}
|
|
1053
|
+
let browserDeps;
|
|
1054
|
+
if (browserConfig && remoteHost) {
|
|
1055
|
+
browserDeps = {
|
|
1056
|
+
executeBrowser: createRemoteBrowserExecutor({ host: remoteHost, token: remoteToken }),
|
|
1057
|
+
};
|
|
1058
|
+
console.log(chalk.dim(`Routing browser automation to remote host ${remoteHost}`));
|
|
1059
|
+
}
|
|
1060
|
+
else if (browserConfig && runOptions.model.startsWith('gemini')) {
|
|
1061
|
+
browserDeps = {
|
|
1062
|
+
executeBrowser: createGeminiWebExecutor({
|
|
1063
|
+
youtube: storedOptions.youtube,
|
|
1064
|
+
generateImage: storedOptions.generateImage,
|
|
1065
|
+
editImage: storedOptions.editImage,
|
|
1066
|
+
outputPath: storedOptions.outputPath,
|
|
1067
|
+
aspectRatio: storedOptions.aspectRatio,
|
|
1068
|
+
showThoughts: storedOptions.geminiShowThoughts,
|
|
1069
|
+
}),
|
|
1070
|
+
};
|
|
1071
|
+
console.log(chalk.dim('Using Gemini web client for browser automation'));
|
|
1072
|
+
if (browserConfig.modelStrategy && browserConfig.modelStrategy !== 'select') {
|
|
1073
|
+
console.log(chalk.dim('Browser model strategy is ignored for Gemini web runs.'));
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
const remoteExecutionActive = Boolean(browserDeps);
|
|
1077
|
+
await sessionStore.ensureStorage();
|
|
1078
|
+
const notifications = deriveNotificationSettingsFromMetadata(metadata, process.env, userConfig.notify);
|
|
1079
|
+
const sessionMeta = await sessionStore.createSession({
|
|
1080
|
+
...runOptions,
|
|
1081
|
+
mode: sessionMode,
|
|
1082
|
+
browserConfig,
|
|
1083
|
+
waitPreference,
|
|
1084
|
+
youtube: storedOptions.youtube,
|
|
1085
|
+
generateImage: storedOptions.generateImage,
|
|
1086
|
+
editImage: storedOptions.editImage,
|
|
1087
|
+
outputPath: storedOptions.outputPath,
|
|
1088
|
+
aspectRatio: storedOptions.aspectRatio,
|
|
1089
|
+
geminiShowThoughts: storedOptions.geminiShowThoughts,
|
|
1090
|
+
}, cwd, notifications, sessionId);
|
|
1091
|
+
const liveRunOptions = {
|
|
1092
|
+
...runOptions,
|
|
1093
|
+
sessionId: sessionMeta.id,
|
|
1094
|
+
effectiveModelId: resolveEffectiveModelIdForRun(runOptions.model, runOptions.effectiveModelId),
|
|
1095
|
+
};
|
|
1096
|
+
const disableDetachEnv = process.env.ORACLE_NO_DETACH === '1';
|
|
1097
|
+
const detachAllowed = remoteExecutionActive
|
|
1098
|
+
? false
|
|
1099
|
+
: shouldDetachSession({
|
|
1100
|
+
engine,
|
|
1101
|
+
model: runOptions.model,
|
|
1102
|
+
waitPreference,
|
|
1103
|
+
disableDetachEnv,
|
|
1104
|
+
});
|
|
1105
|
+
const detached = !detachAllowed
|
|
1106
|
+
? false
|
|
1107
|
+
: await launchDetachedSession(sessionMeta.id).catch((error) => {
|
|
1108
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1109
|
+
console.log(chalk.yellow(`Unable to detach session runner (${message}). Running inline...`));
|
|
1110
|
+
return false;
|
|
1111
|
+
});
|
|
1112
|
+
if (!waitPreference) {
|
|
1113
|
+
if (!detached) {
|
|
1114
|
+
console.log(chalk.red('Unable to start in background; use --wait to run inline.'));
|
|
1115
|
+
process.exitCode = 1;
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
console.log(chalk.blue(`Session running in background. Reattach via: oracle session ${sessionMeta.id}`));
|
|
1119
|
+
console.log(chalk.dim('Pro runs can take up to 60 minutes (usually 10-15). Add --wait to stay attached.'));
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
if (detached === false) {
|
|
1123
|
+
await runInteractiveSession(sessionMeta, liveRunOptions, sessionMode, browserConfig, false, notifications, userConfig, true, browserDeps, cwd);
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
if (detached) {
|
|
1127
|
+
console.log(chalk.blue(`Reattach via: oracle session ${sessionMeta.id}`));
|
|
1128
|
+
await attachSession(sessionMeta.id, { suppressMetadata: true });
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
880
1131
|
async function executeSession(sessionId) {
|
|
881
1132
|
const metadata = await sessionStore.readSession(sessionId);
|
|
882
1133
|
if (!metadata) {
|
|
@@ -927,6 +1178,13 @@ function printDebugHelp(cliName) {
|
|
|
927
1178
|
['--browser-url <url>', 'Alias for --chatgpt-url.'],
|
|
928
1179
|
['--browser-timeout <ms|s|m>', 'Cap total wait time for the assistant response.'],
|
|
929
1180
|
['--browser-input-timeout <ms|s|m>', 'Cap how long we wait for the composer textarea.'],
|
|
1181
|
+
['--browser-recheck-delay <ms|s|m|h>', 'After timeout, wait then revisit the conversation to retry capture.'],
|
|
1182
|
+
['--browser-recheck-timeout <ms|s|m|h>', 'Time budget for the delayed recheck attempt.'],
|
|
1183
|
+
['--browser-reuse-wait <ms|s|m|h>', 'Wait for a shared Chrome profile before launching (parallel runs).'],
|
|
1184
|
+
['--browser-profile-lock-timeout <ms|s|m|h>', 'Wait for the manual-login profile lock before sending.'],
|
|
1185
|
+
['--browser-auto-reattach-delay <ms|s|m|h>', 'Delay before periodic auto-reattach attempts after a timeout.'],
|
|
1186
|
+
['--browser-auto-reattach-interval <ms|s|m|h>', 'Interval between auto-reattach attempts (0 disables).'],
|
|
1187
|
+
['--browser-auto-reattach-timeout <ms|s|m|h>', 'Time budget for each auto-reattach attempt.'],
|
|
930
1188
|
['--browser-cookie-wait <ms|s|m>', 'Wait before retrying cookie sync when Chrome cookies are empty or locked.'],
|
|
931
1189
|
['--browser-no-cookie-sync', 'Skip copying cookies from your main profile.'],
|
|
932
1190
|
['--browser-manual-login', 'Skip cookie copy; reuse a persistent automation profile and log in manually.'],
|
|
@@ -944,19 +1202,36 @@ function printDebugOptionGroup(entries) {
|
|
|
944
1202
|
console.log(` ${label}${description}`);
|
|
945
1203
|
});
|
|
946
1204
|
}
|
|
947
|
-
function resolveWaitFlag({ waitFlag,
|
|
1205
|
+
function resolveWaitFlag({ waitFlag, model, engine, }) {
|
|
948
1206
|
if (waitFlag === true)
|
|
949
1207
|
return true;
|
|
950
|
-
if (
|
|
1208
|
+
if (waitFlag === false)
|
|
951
1209
|
return false;
|
|
952
1210
|
return defaultWaitPreference(model, engine);
|
|
953
1211
|
}
|
|
1212
|
+
function resolveRestartWaitPreference({ waitFlag, storedPreference, model, engine, }) {
|
|
1213
|
+
if (waitFlag === true)
|
|
1214
|
+
return true;
|
|
1215
|
+
if (waitFlag === false)
|
|
1216
|
+
return false;
|
|
1217
|
+
if (typeof storedPreference === 'boolean')
|
|
1218
|
+
return storedPreference;
|
|
1219
|
+
return defaultWaitPreference(model, engine);
|
|
1220
|
+
}
|
|
1221
|
+
function resolveEffectiveModelIdForRun(model, stored) {
|
|
1222
|
+
if (stored)
|
|
1223
|
+
return stored;
|
|
1224
|
+
if (model.startsWith('gemini')) {
|
|
1225
|
+
return resolveGeminiModelId(model);
|
|
1226
|
+
}
|
|
1227
|
+
return isKnownModel(model) ? MODEL_CONFIGS[model].apiModel ?? model : model;
|
|
1228
|
+
}
|
|
954
1229
|
program.action(async function () {
|
|
955
1230
|
const options = this.optsWithGlobals();
|
|
956
1231
|
await runRootCommand(options);
|
|
957
1232
|
});
|
|
958
1233
|
async function main() {
|
|
959
|
-
const parsePromise = program.parseAsync(
|
|
1234
|
+
const parsePromise = program.parseAsync(normalizedArgv);
|
|
960
1235
|
const sigintPromise = once(process, 'SIGINT').then(() => 'sigint');
|
|
961
1236
|
const result = await Promise.race([parsePromise.then(() => 'parsed'), sigintPromise]);
|
|
962
1237
|
if (result === 'sigint') {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import puppeteer from 'puppeteer-core';
|
|
2
|
+
const port = parseInt(process.argv[2] || '52990', 10);
|
|
3
|
+
async function main() {
|
|
4
|
+
const browser = await puppeteer.connect({
|
|
5
|
+
browserURL: `http://127.0.0.1:${port}`,
|
|
6
|
+
defaultViewport: null,
|
|
7
|
+
});
|
|
8
|
+
const pages = await browser.pages();
|
|
9
|
+
let targetPage = null;
|
|
10
|
+
for (const page of pages) {
|
|
11
|
+
const url = page.url();
|
|
12
|
+
if (url.includes('chatgpt.com/c/')) {
|
|
13
|
+
targetPage = page;
|
|
14
|
+
break;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (!targetPage) {
|
|
18
|
+
console.error('ChatGPT conversation page not found');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
console.error('Found page:', await targetPage.url());
|
|
22
|
+
// Extract the last assistant message
|
|
23
|
+
const content = (await targetPage.evaluate(() => {
|
|
24
|
+
// Try multiple selectors for ChatGPT's assistant messages
|
|
25
|
+
const selectors = [
|
|
26
|
+
'[data-message-author-role="assistant"] .markdown',
|
|
27
|
+
'[data-message-author-role="assistant"]',
|
|
28
|
+
'.agent-turn .markdown',
|
|
29
|
+
'.agent-turn',
|
|
30
|
+
];
|
|
31
|
+
for (const selector of selectors) {
|
|
32
|
+
const elements = document.querySelectorAll(selector);
|
|
33
|
+
if (elements.length > 0) {
|
|
34
|
+
const lastEl = elements[elements.length - 1];
|
|
35
|
+
return {
|
|
36
|
+
selector,
|
|
37
|
+
count: elements.length,
|
|
38
|
+
text: lastEl.innerText,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Debug: show what's on the page
|
|
43
|
+
const body = document.body.innerHTML;
|
|
44
|
+
return { error: 'No messages found', bodyLength: body.length, sample: body.slice(0, 2000) };
|
|
45
|
+
}));
|
|
46
|
+
if ('error' in content) {
|
|
47
|
+
console.error('Error:', JSON.stringify(content, null, 2));
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
console.log(content.text);
|
|
51
|
+
browser.disconnect();
|
|
52
|
+
}
|
|
53
|
+
main().catch(console.error);
|