@steipete/oracle 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +61 -48
- package/dist/bin/oracle-cli.js +455 -402
- package/dist/bin/oracle-mcp.js +2 -2
- package/dist/bin/oracle.js +165 -279
- package/dist/scripts/agent-send.js +31 -31
- package/dist/scripts/check.js +6 -6
- package/dist/scripts/debug/extract-chatgpt-response.js +10 -10
- package/dist/scripts/docs-list.js +30 -30
- package/dist/scripts/git-policy.js +25 -23
- package/dist/scripts/run-cli.js +8 -8
- package/dist/scripts/runner.js +203 -195
- package/dist/scripts/test-browser.js +21 -18
- package/dist/scripts/test-remote-chrome.js +20 -20
- package/dist/src/bridge/connection.js +18 -18
- package/dist/src/bridge/userConfigFile.js +7 -7
- package/dist/src/browser/actions/assistantResponse.js +149 -101
- package/dist/src/browser/actions/attachmentDataTransfer.js +49 -47
- package/dist/src/browser/actions/attachments.js +246 -150
- package/dist/src/browser/actions/domEvents.js +2 -2
- package/dist/src/browser/actions/modelSelection.js +275 -117
- package/dist/src/browser/actions/navigation.js +161 -137
- package/dist/src/browser/actions/promptComposer.js +100 -64
- package/dist/src/browser/actions/remoteFileTransfer.js +10 -10
- package/dist/src/browser/actions/thinkingTime.js +207 -110
- package/dist/src/browser/chromeLifecycle.js +62 -60
- package/dist/src/browser/config.js +34 -15
- package/dist/src/browser/constants.js +17 -12
- package/dist/src/browser/cookies.js +19 -19
- package/dist/src/browser/detect.js +62 -62
- package/dist/src/browser/domDebug.js +1 -1
- package/dist/src/browser/index.js +390 -295
- package/dist/src/browser/modelStrategy.js +1 -1
- package/dist/src/browser/pageActions.js +5 -5
- package/dist/src/browser/policies.js +16 -13
- package/dist/src/browser/profileState.js +44 -39
- package/dist/src/browser/prompt.js +72 -42
- package/dist/src/browser/promptSummary.js +5 -5
- package/dist/src/browser/providerDomFlow.js +1 -1
- package/dist/src/browser/providers/chatgptDomProvider.js +9 -9
- package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +51 -42
- package/dist/src/browser/providers/index.js +2 -2
- package/dist/src/browser/reattach.js +67 -34
- package/dist/src/browser/reattachHelpers.js +31 -26
- package/dist/src/browser/sessionRunner.js +37 -25
- package/dist/src/browser/utils.js +9 -9
- package/dist/src/browserMode.js +1 -1
- package/dist/src/cli/bridge/claudeConfig.js +16 -16
- package/dist/src/cli/bridge/client.js +28 -20
- package/dist/src/cli/bridge/codexConfig.js +16 -16
- package/dist/src/cli/bridge/doctor.js +47 -39
- package/dist/src/cli/bridge/host.js +58 -56
- package/dist/src/cli/browserConfig.js +62 -48
- package/dist/src/cli/browserDefaults.js +27 -26
- package/dist/src/cli/bundleWarnings.js +1 -1
- package/dist/src/cli/clipboard.js +11 -2
- package/dist/src/cli/detach.js +2 -2
- package/dist/src/cli/dryRun.js +29 -25
- package/dist/src/cli/duplicatePromptGuard.js +3 -3
- package/dist/src/cli/engine.js +9 -9
- package/dist/src/cli/errorUtils.js +1 -1
- package/dist/src/cli/fileSize.js +3 -3
- package/dist/src/cli/format.js +2 -2
- package/dist/src/cli/help.js +28 -28
- package/dist/src/cli/hiddenAliases.js +3 -3
- package/dist/src/cli/markdownBundle.js +7 -7
- package/dist/src/cli/markdownRenderer.js +15 -15
- package/dist/src/cli/notifier.js +77 -67
- package/dist/src/cli/options.js +127 -106
- package/dist/src/cli/oscUtils.js +1 -1
- package/dist/src/cli/promptRequirement.js +2 -2
- package/dist/src/cli/renderOutput.js +1 -1
- package/dist/src/cli/rootAlias.js +1 -1
- package/dist/src/cli/runOptions.js +32 -28
- package/dist/src/cli/sessionCommand.js +31 -21
- package/dist/src/cli/sessionDisplay.js +95 -81
- package/dist/src/cli/sessionLineage.js +6 -2
- package/dist/src/cli/sessionRunner.js +103 -93
- package/dist/src/cli/sessionTable.js +26 -23
- package/dist/src/cli/stdin.js +22 -0
- package/dist/src/cli/tagline.js +121 -124
- package/dist/src/cli/tui/index.js +139 -128
- package/dist/src/cli/writeOutputPath.js +5 -5
- package/dist/src/config.js +7 -7
- package/dist/src/gemini-web/browserSessionManager.js +19 -15
- package/dist/src/gemini-web/client.js +76 -70
- package/dist/src/gemini-web/executionMode.js +6 -8
- package/dist/src/gemini-web/executor.js +98 -93
- package/dist/src/gemini-web/index.js +1 -1
- package/dist/src/mcp/server.js +16 -12
- package/dist/src/mcp/tools/consult.js +51 -47
- package/dist/src/mcp/tools/sessionResources.js +12 -12
- package/dist/src/mcp/tools/sessions.js +26 -17
- package/dist/src/mcp/types.js +5 -5
- package/dist/src/mcp/utils.js +15 -7
- package/dist/src/oracle/background.js +15 -15
- package/dist/src/oracle/claude.js +53 -25
- package/dist/src/oracle/client.js +50 -41
- package/dist/src/oracle/config.js +96 -66
- package/dist/src/oracle/errors.js +38 -38
- package/dist/src/oracle/files.js +55 -46
- package/dist/src/oracle/finishLine.js +10 -8
- package/dist/src/oracle/format.js +3 -3
- package/dist/src/oracle/gemini.js +37 -33
- package/dist/src/oracle/logging.js +7 -7
- package/dist/src/oracle/markdown.js +28 -28
- package/dist/src/oracle/modelResolver.js +16 -16
- package/dist/src/oracle/multiModelRunner.js +12 -12
- package/dist/src/oracle/oscProgress.js +8 -8
- package/dist/src/oracle/promptAssembly.js +6 -3
- package/dist/src/oracle/request.js +16 -13
- package/dist/src/oracle/run.js +156 -134
- package/dist/src/oracle/runUtils.js +8 -5
- package/dist/src/oracle/tokenEstimate.js +6 -6
- package/dist/src/oracle/tokenStats.js +5 -5
- package/dist/src/oracle/tokenStringifier.js +5 -5
- package/dist/src/oracle.js +12 -12
- package/dist/src/oracleHome.js +3 -3
- package/dist/src/remote/client.js +25 -25
- package/dist/src/remote/health.js +20 -20
- package/dist/src/remote/remoteServiceConfig.js +9 -9
- package/dist/src/remote/server.js +129 -118
- package/dist/src/sessionManager.js +77 -75
- package/dist/src/sessionStore.js +3 -3
- package/dist/src/version.js +10 -10
- package/dist/vendor/oracle-notifier/README.md +2 -0
- package/package.json +66 -62
- package/vendor/oracle-notifier/README.md +2 -0
- package/dist/markdansi/types/index.js +0 -4
- package/dist/oracle/bin/oracle-cli.js +0 -472
- package/dist/oracle/src/browser/actions/assistantResponse.js +0 -471
- package/dist/oracle/src/browser/actions/attachments.js +0 -82
- package/dist/oracle/src/browser/actions/modelSelection.js +0 -190
- package/dist/oracle/src/browser/actions/navigation.js +0 -75
- package/dist/oracle/src/browser/actions/promptComposer.js +0 -167
- package/dist/oracle/src/browser/chromeLifecycle.js +0 -104
- package/dist/oracle/src/browser/config.js +0 -33
- package/dist/oracle/src/browser/constants.js +0 -40
- package/dist/oracle/src/browser/cookies.js +0 -210
- package/dist/oracle/src/browser/domDebug.js +0 -36
- package/dist/oracle/src/browser/index.js +0 -331
- package/dist/oracle/src/browser/pageActions.js +0 -5
- package/dist/oracle/src/browser/prompt.js +0 -88
- package/dist/oracle/src/browser/promptSummary.js +0 -20
- package/dist/oracle/src/browser/sessionRunner.js +0 -80
- package/dist/oracle/src/browser/types.js +0 -1
- package/dist/oracle/src/browser/utils.js +0 -62
- package/dist/oracle/src/browserMode.js +0 -1
- package/dist/oracle/src/cli/browserConfig.js +0 -44
- package/dist/oracle/src/cli/dryRun.js +0 -59
- package/dist/oracle/src/cli/engine.js +0 -17
- package/dist/oracle/src/cli/errorUtils.js +0 -9
- package/dist/oracle/src/cli/help.js +0 -70
- package/dist/oracle/src/cli/markdownRenderer.js +0 -15
- package/dist/oracle/src/cli/options.js +0 -103
- package/dist/oracle/src/cli/promptRequirement.js +0 -14
- package/dist/oracle/src/cli/rootAlias.js +0 -30
- package/dist/oracle/src/cli/sessionCommand.js +0 -77
- package/dist/oracle/src/cli/sessionDisplay.js +0 -270
- package/dist/oracle/src/cli/sessionRunner.js +0 -94
- package/dist/oracle/src/heartbeat.js +0 -43
- package/dist/oracle/src/oracle/client.js +0 -48
- package/dist/oracle/src/oracle/config.js +0 -29
- package/dist/oracle/src/oracle/errors.js +0 -101
- package/dist/oracle/src/oracle/files.js +0 -220
- package/dist/oracle/src/oracle/format.js +0 -33
- package/dist/oracle/src/oracle/fsAdapter.js +0 -7
- package/dist/oracle/src/oracle/oscProgress.js +0 -60
- package/dist/oracle/src/oracle/request.js +0 -48
- package/dist/oracle/src/oracle/run.js +0 -444
- package/dist/oracle/src/oracle/tokenStats.js +0 -39
- package/dist/oracle/src/oracle/types.js +0 -1
- package/dist/oracle/src/oracle.js +0 -9
- package/dist/oracle/src/sessionManager.js +0 -205
- package/dist/oracle/src/version.js +0 -39
- package/dist/scripts/chrome/browser-tools.js +0 -295
- package/dist/src/browser/profileSync.js +0 -141
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
import kleur from
|
|
2
|
-
import fs from
|
|
3
|
-
import path from
|
|
4
|
-
import { runOracle, OracleResponseError, OracleTransportError, extractResponseMetadata, asOracleUserError, extractTextOutput, } from
|
|
5
|
-
import { runBrowserSessionExecution } from
|
|
6
|
-
import { renderMarkdownAnsi } from
|
|
7
|
-
import { formatResponseMetadata, formatTransportMetadata } from
|
|
8
|
-
import { markErrorLogged } from
|
|
9
|
-
import { sendSessionNotification, deriveNotificationSettingsFromMetadata, } from
|
|
10
|
-
import { sessionStore } from
|
|
11
|
-
import { wait } from
|
|
12
|
-
import { runMultiModelApiSession } from
|
|
13
|
-
import { MODEL_CONFIGS, DEFAULT_SYSTEM_PROMPT } from
|
|
14
|
-
import { isKnownModel } from
|
|
15
|
-
import { resolveModelConfig } from
|
|
16
|
-
import { buildPrompt, buildRequestBody } from
|
|
17
|
-
import { estimateRequestTokens } from
|
|
18
|
-
import { formatTokenEstimate, formatTokenValue } from
|
|
19
|
-
import { formatFinishLine } from
|
|
20
|
-
import { sanitizeOscProgress } from
|
|
21
|
-
import { readFiles } from
|
|
22
|
-
import { cwd as getCwd } from
|
|
23
|
-
import { resumeBrowserSession } from
|
|
24
|
-
import { estimateTokenCount } from
|
|
25
|
-
import { formatElapsed } from
|
|
1
|
+
import kleur from "kleur";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { runOracle, OracleResponseError, OracleTransportError, extractResponseMetadata, asOracleUserError, extractTextOutput, } from "../oracle.js";
|
|
5
|
+
import { runBrowserSessionExecution, } from "../browser/sessionRunner.js";
|
|
6
|
+
import { renderMarkdownAnsi } from "./markdownRenderer.js";
|
|
7
|
+
import { formatResponseMetadata, formatTransportMetadata } from "./sessionDisplay.js";
|
|
8
|
+
import { markErrorLogged } from "./errorUtils.js";
|
|
9
|
+
import { sendSessionNotification, deriveNotificationSettingsFromMetadata, } from "./notifier.js";
|
|
10
|
+
import { sessionStore } from "../sessionStore.js";
|
|
11
|
+
import { wait } from "../sessionManager.js";
|
|
12
|
+
import { runMultiModelApiSession } from "../oracle/multiModelRunner.js";
|
|
13
|
+
import { MODEL_CONFIGS, DEFAULT_SYSTEM_PROMPT } from "../oracle/config.js";
|
|
14
|
+
import { isKnownModel } from "../oracle/modelResolver.js";
|
|
15
|
+
import { resolveModelConfig } from "../oracle/modelResolver.js";
|
|
16
|
+
import { buildPrompt, buildRequestBody } from "../oracle/request.js";
|
|
17
|
+
import { estimateRequestTokens } from "../oracle/tokenEstimate.js";
|
|
18
|
+
import { formatTokenEstimate, formatTokenValue } from "../oracle/runUtils.js";
|
|
19
|
+
import { formatFinishLine } from "../oracle/finishLine.js";
|
|
20
|
+
import { sanitizeOscProgress } from "./oscUtils.js";
|
|
21
|
+
import { readFiles } from "../oracle/files.js";
|
|
22
|
+
import { cwd as getCwd } from "node:process";
|
|
23
|
+
import { resumeBrowserSession } from "../browser/reattach.js";
|
|
24
|
+
import { estimateTokenCount } from "../browser/utils.js";
|
|
25
|
+
import { formatElapsed } from "../oracle/format.js";
|
|
26
26
|
const isTty = process.stdout.isTTY;
|
|
27
27
|
const dim = (text) => (isTty ? kleur.dim(text) : text);
|
|
28
28
|
export async function performSessionRun({ sessionMeta, runOptions, mode, browserConfig, cwd, log, write, version, notifications, browserDeps, muteStdout = false, }) {
|
|
@@ -32,7 +32,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
32
32
|
return muteStdout ? true : process.stdout.write(chunk);
|
|
33
33
|
};
|
|
34
34
|
await sessionStore.updateSession(sessionMeta.id, {
|
|
35
|
-
status:
|
|
35
|
+
status: "running",
|
|
36
36
|
startedAt: new Date().toISOString(),
|
|
37
37
|
mode,
|
|
38
38
|
...(browserConfig ? { browser: { config: browserConfig } } : {}),
|
|
@@ -40,13 +40,13 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
40
40
|
const notificationSettings = notifications ?? deriveNotificationSettingsFromMetadata(sessionMeta, process.env);
|
|
41
41
|
const modelForStatus = runOptions.model ?? sessionMeta.model;
|
|
42
42
|
try {
|
|
43
|
-
if (mode ===
|
|
43
|
+
if (mode === "browser") {
|
|
44
44
|
if (!browserConfig) {
|
|
45
|
-
throw new Error(
|
|
45
|
+
throw new Error("Missing browser configuration for session.");
|
|
46
46
|
}
|
|
47
47
|
if (modelForStatus) {
|
|
48
48
|
await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
|
|
49
|
-
status:
|
|
49
|
+
status: "running",
|
|
50
50
|
startedAt: new Date().toISOString(),
|
|
51
51
|
});
|
|
52
52
|
}
|
|
@@ -54,7 +54,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
54
54
|
...browserDeps,
|
|
55
55
|
persistRuntimeHint: async (runtime) => {
|
|
56
56
|
await sessionStore.updateSession(sessionMeta.id, {
|
|
57
|
-
status:
|
|
57
|
+
status: "running",
|
|
58
58
|
browser: { config: browserConfig, runtime },
|
|
59
59
|
});
|
|
60
60
|
},
|
|
@@ -62,13 +62,13 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
62
62
|
const result = await runBrowserSessionExecution({ runOptions, browserConfig, cwd, log }, runnerDeps);
|
|
63
63
|
if (modelForStatus) {
|
|
64
64
|
await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
|
|
65
|
-
status:
|
|
65
|
+
status: "completed",
|
|
66
66
|
completedAt: new Date().toISOString(),
|
|
67
67
|
usage: result.usage,
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
70
|
await sessionStore.updateSession(sessionMeta.id, {
|
|
71
|
-
status:
|
|
71
|
+
status: "completed",
|
|
72
72
|
completedAt: new Date().toISOString(),
|
|
73
73
|
usage: result.usage,
|
|
74
74
|
elapsedMs: result.elapsedMs,
|
|
@@ -80,7 +80,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
80
80
|
transport: undefined,
|
|
81
81
|
error: undefined,
|
|
82
82
|
});
|
|
83
|
-
await writeAssistantOutput(runOptions.writeOutputPath, result.answerText ??
|
|
83
|
+
await writeAssistantOutput(runOptions.writeOutputPath, result.answerText ?? "", log);
|
|
84
84
|
await sendSessionNotification({
|
|
85
85
|
sessionId: sessionMeta.id,
|
|
86
86
|
sessionName: sessionMeta.options?.slug ?? sessionMeta.id,
|
|
@@ -95,7 +95,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
95
95
|
if (multiModels.length > 1) {
|
|
96
96
|
const [primaryModel] = multiModels;
|
|
97
97
|
if (!primaryModel) {
|
|
98
|
-
throw new Error(
|
|
98
|
+
throw new Error("Missing model name for multi-model run.");
|
|
99
99
|
}
|
|
100
100
|
const modelConfig = await resolveModelConfig(primaryModel, {
|
|
101
101
|
baseUrl: runOptions.baseUrl,
|
|
@@ -116,30 +116,30 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
116
116
|
storeResponse: runOptions.background,
|
|
117
117
|
});
|
|
118
118
|
const estimatedTokens = estimateRequestTokens(requestBody, modelConfig);
|
|
119
|
-
const tokenLabel = formatTokenEstimate(estimatedTokens, (text) =>
|
|
120
|
-
const filesPhrase = files.length === 0 ?
|
|
121
|
-
const modelsLabel = multiModels.join(
|
|
119
|
+
const tokenLabel = formatTokenEstimate(estimatedTokens, (text) => isTty ? kleur.green(text) : text);
|
|
120
|
+
const filesPhrase = files.length === 0 ? "no files" : `${files.length} files`;
|
|
121
|
+
const modelsLabel = multiModels.join(", ");
|
|
122
122
|
log(`Calling ${isTty ? kleur.cyan(modelsLabel) : modelsLabel} — ${tokenLabel} tokens, ${filesPhrase}.`);
|
|
123
123
|
const multiRunTips = [];
|
|
124
124
|
if (files.length === 0) {
|
|
125
|
-
multiRunTips.push(
|
|
125
|
+
multiRunTips.push("Tip: no files attached — Oracle works best with project context. Add files via --file path/to/code or docs.");
|
|
126
126
|
}
|
|
127
127
|
const shortPrompt = (runOptions.prompt?.trim().length ?? 0) < 80;
|
|
128
128
|
if (shortPrompt) {
|
|
129
|
-
multiRunTips.push(
|
|
129
|
+
multiRunTips.push("Tip: brief prompts often yield generic answers — aim for 6–30 sentences and attach key files.");
|
|
130
130
|
}
|
|
131
131
|
for (const tip of multiRunTips) {
|
|
132
132
|
log(dim(tip));
|
|
133
133
|
}
|
|
134
134
|
// Surface long-running model expectations up front so users know why a response might lag.
|
|
135
|
-
const longRunningModels = multiModels.filter((model) => isKnownModel(model) && MODEL_CONFIGS[model]?.reasoning?.effort ===
|
|
135
|
+
const longRunningModels = multiModels.filter((model) => isKnownModel(model) && MODEL_CONFIGS[model]?.reasoning?.effort === "high");
|
|
136
136
|
if (longRunningModels.length > 0) {
|
|
137
137
|
for (const model of longRunningModels) {
|
|
138
|
-
log(
|
|
138
|
+
log("");
|
|
139
139
|
const headingLabel = `[${model}]`;
|
|
140
140
|
log(isTty ? kleur.bold(headingLabel) : headingLabel);
|
|
141
|
-
log(dim(
|
|
142
|
-
log(dim(
|
|
141
|
+
log(dim("This model can take up to 60 minutes (usually replies much faster)."));
|
|
142
|
+
log(dim("Press Ctrl+C to cancel."));
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
const shouldStreamInline = !muteStdout && process.stdout.isTTY;
|
|
@@ -152,7 +152,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
152
152
|
return;
|
|
153
153
|
printedModels.add(model);
|
|
154
154
|
const body = stripOscProgress(await sessionStore.readModelLog(sessionMeta.id, model));
|
|
155
|
-
log(
|
|
155
|
+
log("");
|
|
156
156
|
const fallback = answerFallbacks.get(model);
|
|
157
157
|
const hasBody = body.length > 0;
|
|
158
158
|
if (!hasBody && !fallback) {
|
|
@@ -162,11 +162,11 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
162
162
|
const headingLabel = `[${model}]`;
|
|
163
163
|
const heading = shouldStreamInline ? kleur.bold(headingLabel) : headingLabel;
|
|
164
164
|
log(heading);
|
|
165
|
-
const content = hasBody ? body : fallback ??
|
|
165
|
+
const content = hasBody ? body : (fallback ?? "");
|
|
166
166
|
const printable = shouldRenderMarkdown ? renderMarkdownAnsi(content) : content;
|
|
167
167
|
writeInline(printable);
|
|
168
|
-
if (!printable.endsWith(
|
|
169
|
-
log(
|
|
168
|
+
if (!printable.endsWith("\n")) {
|
|
169
|
+
log("");
|
|
170
170
|
}
|
|
171
171
|
};
|
|
172
172
|
const summary = await runMultiModelApiSession({
|
|
@@ -192,7 +192,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
192
192
|
// If we couldn't stream inline (e.g., non-TTY), print all logs after completion.
|
|
193
193
|
for (const [index, result] of summary.fulfilled.entries()) {
|
|
194
194
|
if (index > 0) {
|
|
195
|
-
log(
|
|
195
|
+
log("");
|
|
196
196
|
}
|
|
197
197
|
await printModelLog(result.model);
|
|
198
198
|
}
|
|
@@ -216,14 +216,18 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
216
216
|
reasoning_tokens: aggregateUsage.reasoningTokens,
|
|
217
217
|
total_tokens: aggregateUsage.totalTokens,
|
|
218
218
|
}, idx))
|
|
219
|
-
.join(
|
|
219
|
+
.join("/");
|
|
220
220
|
const tokensPart = (() => {
|
|
221
|
-
const parts = tokensDisplay.split(
|
|
221
|
+
const parts = tokensDisplay.split("/");
|
|
222
222
|
if (parts.length !== 4)
|
|
223
223
|
return tokensDisplay;
|
|
224
224
|
return `↑${parts[0]} ↓${parts[1]} ↻${parts[2]} Δ${parts[3]}`;
|
|
225
225
|
})();
|
|
226
|
-
const statusColor = summary.rejected.length === 0
|
|
226
|
+
const statusColor = summary.rejected.length === 0
|
|
227
|
+
? kleur.green
|
|
228
|
+
: summary.fulfilled.length > 0
|
|
229
|
+
? kleur.yellow
|
|
230
|
+
: kleur.red;
|
|
227
231
|
const overallText = `${summary.fulfilled.length}/${multiModels.length} models`;
|
|
228
232
|
const { line1 } = formatFinishLine({
|
|
229
233
|
elapsedMs: summary.elapsedMs,
|
|
@@ -234,7 +238,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
234
238
|
log(statusColor(line1));
|
|
235
239
|
const hasFailure = summary.rejected.length > 0;
|
|
236
240
|
await sessionStore.updateSession(sessionMeta.id, {
|
|
237
|
-
status: hasFailure ?
|
|
241
|
+
status: hasFailure ? "error" : "completed",
|
|
238
242
|
completedAt: new Date().toISOString(),
|
|
239
243
|
usage: aggregateUsage,
|
|
240
244
|
elapsedMs: summary.elapsedMs,
|
|
@@ -261,7 +265,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
261
265
|
}
|
|
262
266
|
}
|
|
263
267
|
if (savedOutputs.length > 0) {
|
|
264
|
-
log(dim(
|
|
268
|
+
log(dim("Saved outputs:"));
|
|
265
269
|
for (const item of savedOutputs) {
|
|
266
270
|
log(dim(`- ${item.model} -> ${item.path}`));
|
|
267
271
|
}
|
|
@@ -278,7 +282,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
278
282
|
: runOptions;
|
|
279
283
|
if (modelForStatus && singleModelOverride == null) {
|
|
280
284
|
await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
|
|
281
|
-
status:
|
|
285
|
+
status: "running",
|
|
282
286
|
startedAt: new Date().toISOString(),
|
|
283
287
|
});
|
|
284
288
|
}
|
|
@@ -288,11 +292,11 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
288
292
|
write,
|
|
289
293
|
allowStdout: !muteStdout,
|
|
290
294
|
});
|
|
291
|
-
if (result.mode !==
|
|
292
|
-
throw new Error(
|
|
295
|
+
if (result.mode !== "live") {
|
|
296
|
+
throw new Error("Unexpected preview result while running a session.");
|
|
293
297
|
}
|
|
294
298
|
await sessionStore.updateSession(sessionMeta.id, {
|
|
295
|
-
status:
|
|
299
|
+
status: "completed",
|
|
296
300
|
completedAt: new Date().toISOString(),
|
|
297
301
|
usage: result.usage,
|
|
298
302
|
elapsedMs: result.elapsedMs,
|
|
@@ -302,7 +306,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
302
306
|
});
|
|
303
307
|
if (modelForStatus && singleModelOverride == null) {
|
|
304
308
|
await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
|
|
305
|
-
status:
|
|
309
|
+
status: "completed",
|
|
306
310
|
completedAt: new Date().toISOString(),
|
|
307
311
|
usage: result.usage,
|
|
308
312
|
});
|
|
@@ -323,49 +327,53 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
323
327
|
log(`ERROR: ${message}`);
|
|
324
328
|
markErrorLogged(error);
|
|
325
329
|
const userError = asOracleUserError(error);
|
|
326
|
-
const connectionLost = userError?.category ===
|
|
327
|
-
|
|
328
|
-
const
|
|
329
|
-
userError.details?.stage ===
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
330
|
+
const connectionLost = userError?.category === "browser-automation" &&
|
|
331
|
+
userError.details?.stage === "connection-lost";
|
|
332
|
+
const assistantTimeout = userError?.category === "browser-automation" &&
|
|
333
|
+
userError.details?.stage === "assistant-timeout";
|
|
334
|
+
const cloudflareChallenge = userError?.category === "browser-automation" &&
|
|
335
|
+
userError.details?.stage === "cloudflare-challenge";
|
|
336
|
+
if (connectionLost && mode === "browser") {
|
|
337
|
+
const runtime = userError.details
|
|
338
|
+
?.runtime;
|
|
339
|
+
log(dim("Chrome disconnected before completion; keeping session running for reattach."));
|
|
333
340
|
if (modelForStatus) {
|
|
334
341
|
await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
|
|
335
|
-
status:
|
|
342
|
+
status: "running",
|
|
336
343
|
completedAt: undefined,
|
|
337
344
|
});
|
|
338
345
|
}
|
|
339
346
|
await sessionStore.updateSession(sessionMeta.id, {
|
|
340
|
-
status:
|
|
347
|
+
status: "running",
|
|
341
348
|
errorMessage: message,
|
|
342
349
|
mode,
|
|
343
350
|
browser: {
|
|
344
351
|
config: browserConfig,
|
|
345
352
|
runtime: runtime ?? sessionMeta.browser?.runtime,
|
|
346
353
|
},
|
|
347
|
-
response: { status:
|
|
354
|
+
response: { status: "running", incompleteReason: "chrome-disconnected" },
|
|
348
355
|
});
|
|
349
356
|
return;
|
|
350
357
|
}
|
|
351
|
-
if (assistantTimeout && mode ===
|
|
352
|
-
const runtime = userError.details
|
|
353
|
-
|
|
358
|
+
if (assistantTimeout && mode === "browser") {
|
|
359
|
+
const runtime = userError.details
|
|
360
|
+
?.runtime;
|
|
361
|
+
log(dim("Assistant response timed out; keeping session running for reattach."));
|
|
354
362
|
if (modelForStatus) {
|
|
355
363
|
await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
|
|
356
|
-
status:
|
|
364
|
+
status: "running",
|
|
357
365
|
completedAt: undefined,
|
|
358
366
|
});
|
|
359
367
|
}
|
|
360
368
|
await sessionStore.updateSession(sessionMeta.id, {
|
|
361
|
-
status:
|
|
369
|
+
status: "running",
|
|
362
370
|
errorMessage: message,
|
|
363
371
|
mode,
|
|
364
372
|
browser: {
|
|
365
373
|
config: browserConfig,
|
|
366
374
|
runtime: runtime ?? sessionMeta.browser?.runtime,
|
|
367
375
|
},
|
|
368
|
-
response: { status:
|
|
376
|
+
response: { status: "running", incompleteReason: "assistant-timeout" },
|
|
369
377
|
});
|
|
370
378
|
const autoReattachIntervalMs = browserConfig?.autoReattachIntervalMs ?? 0;
|
|
371
379
|
if (autoReattachIntervalMs > 0) {
|
|
@@ -386,9 +394,9 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
386
394
|
log(dim(`Reattach later with: oracle session ${sessionMeta.id}`));
|
|
387
395
|
return;
|
|
388
396
|
}
|
|
389
|
-
if (cloudflareChallenge && mode ===
|
|
397
|
+
if (cloudflareChallenge && mode === "browser") {
|
|
390
398
|
const details = userError.details;
|
|
391
|
-
log(dim(
|
|
399
|
+
log(dim("Cloudflare challenge detected; browser left running so you can complete the check."));
|
|
392
400
|
if (details?.reuseProfileHint) {
|
|
393
401
|
log(dim(`Reuse this browser profile with: ${details.reuseProfileHint}`));
|
|
394
402
|
}
|
|
@@ -406,9 +414,11 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
406
414
|
if (transportLine) {
|
|
407
415
|
log(dim(`Transport: ${transportLine}`));
|
|
408
416
|
}
|
|
409
|
-
const browserRuntime = mode ===
|
|
417
|
+
const browserRuntime = mode === "browser"
|
|
418
|
+
? userError?.details?.runtime
|
|
419
|
+
: undefined;
|
|
410
420
|
await sessionStore.updateSession(sessionMeta.id, {
|
|
411
|
-
status:
|
|
421
|
+
status: "error",
|
|
412
422
|
completedAt: new Date().toISOString(),
|
|
413
423
|
errorMessage: message,
|
|
414
424
|
mode,
|
|
@@ -430,7 +440,7 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
430
440
|
});
|
|
431
441
|
if (modelForStatus) {
|
|
432
442
|
await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
|
|
433
|
-
status:
|
|
443
|
+
status: "error",
|
|
434
444
|
completedAt: new Date().toISOString(),
|
|
435
445
|
});
|
|
436
446
|
}
|
|
@@ -444,7 +454,7 @@ async function writeAssistantOutput(targetPath, content, log) {
|
|
|
444
454
|
if (!targetPath)
|
|
445
455
|
return;
|
|
446
456
|
if (!content || content.trim().length === 0) {
|
|
447
|
-
log(dim(
|
|
457
|
+
log(dim("write-output skipped: no assistant content to save."));
|
|
448
458
|
return;
|
|
449
459
|
}
|
|
450
460
|
const normalizedTarget = path.resolve(targetPath);
|
|
@@ -456,8 +466,8 @@ async function writeAssistantOutput(targetPath, content, log) {
|
|
|
456
466
|
}
|
|
457
467
|
try {
|
|
458
468
|
await fs.mkdir(path.dirname(normalizedTarget), { recursive: true });
|
|
459
|
-
const payload = content.endsWith(
|
|
460
|
-
await fs.writeFile(normalizedTarget, payload,
|
|
469
|
+
const payload = content.endsWith("\n") ? content : `${content}\n`;
|
|
470
|
+
await fs.writeFile(normalizedTarget, payload, "utf8");
|
|
461
471
|
log(dim(`Saved assistant output to ${normalizedTarget}`));
|
|
462
472
|
return normalizedTarget;
|
|
463
473
|
}
|
|
@@ -468,8 +478,8 @@ async function writeAssistantOutput(targetPath, content, log) {
|
|
|
468
478
|
if (fallbackPath) {
|
|
469
479
|
try {
|
|
470
480
|
await fs.mkdir(path.dirname(fallbackPath), { recursive: true });
|
|
471
|
-
const payload = content.endsWith(
|
|
472
|
-
await fs.writeFile(fallbackPath, payload,
|
|
481
|
+
const payload = content.endsWith("\n") ? content : `${content}\n`;
|
|
482
|
+
await fs.writeFile(fallbackPath, payload, "utf8");
|
|
473
483
|
log(dim(`write-output fallback to ${fallbackPath} (original failed: ${reason})`));
|
|
474
484
|
return fallbackPath;
|
|
475
485
|
}
|
|
@@ -485,7 +495,7 @@ async function writeAssistantOutput(targetPath, content, log) {
|
|
|
485
495
|
}
|
|
486
496
|
async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig, runOptions, modelForStatus, notificationSettings, log, }) {
|
|
487
497
|
if (!runtime || !browserConfig) {
|
|
488
|
-
log(dim(
|
|
498
|
+
log(dim("Auto-reattach disabled: missing runtime or browser config."));
|
|
489
499
|
return false;
|
|
490
500
|
}
|
|
491
501
|
const delayMs = Math.max(0, browserConfig.autoReattachDelayMs ?? 0);
|
|
@@ -526,16 +536,16 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
|
|
|
526
536
|
const result = await resumeBrowserSession(runtime, reattachConfig, logger, {
|
|
527
537
|
promptPreview: sessionMeta.promptPreview,
|
|
528
538
|
});
|
|
529
|
-
const answerText = result.answerMarkdown || result.answerText ||
|
|
539
|
+
const answerText = result.answerMarkdown || result.answerText || "";
|
|
530
540
|
const outputTokens = estimateTokenCount(answerText);
|
|
531
541
|
const logWriter = sessionStore.createLogWriter(sessionMeta.id);
|
|
532
542
|
logWriter.logLine(`[auto-reattach] captured assistant response on attempt ${attempt}`);
|
|
533
|
-
logWriter.logLine(
|
|
543
|
+
logWriter.logLine("Answer:");
|
|
534
544
|
logWriter.logLine(answerText);
|
|
535
545
|
logWriter.stream.end();
|
|
536
546
|
if (modelForStatus) {
|
|
537
547
|
await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
|
|
538
|
-
status:
|
|
548
|
+
status: "completed",
|
|
539
549
|
completedAt: new Date().toISOString(),
|
|
540
550
|
usage: {
|
|
541
551
|
inputTokens: 0,
|
|
@@ -546,7 +556,7 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
|
|
|
546
556
|
});
|
|
547
557
|
}
|
|
548
558
|
await sessionStore.updateSession(sessionMeta.id, {
|
|
549
|
-
status:
|
|
559
|
+
status: "completed",
|
|
550
560
|
completedAt: new Date().toISOString(),
|
|
551
561
|
usage: {
|
|
552
562
|
inputTokens: 0,
|
|
@@ -558,7 +568,7 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
|
|
|
558
568
|
config: browserConfig,
|
|
559
569
|
runtime,
|
|
560
570
|
},
|
|
561
|
-
response: { status:
|
|
571
|
+
response: { status: "completed" },
|
|
562
572
|
error: undefined,
|
|
563
573
|
transport: undefined,
|
|
564
574
|
});
|
|
@@ -566,7 +576,7 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
|
|
|
566
576
|
await sendSessionNotification({
|
|
567
577
|
sessionId: sessionMeta.id,
|
|
568
578
|
sessionName: sessionMeta.options?.slug ?? sessionMeta.id,
|
|
569
|
-
mode: sessionMeta.mode ??
|
|
579
|
+
mode: sessionMeta.mode ?? "browser",
|
|
570
580
|
model: sessionMeta.model ?? runOptions.model,
|
|
571
581
|
usage: {
|
|
572
582
|
inputTokens: 0,
|
|
@@ -574,7 +584,7 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
|
|
|
574
584
|
},
|
|
575
585
|
characters: answerText.length,
|
|
576
586
|
}, notificationSettings, log, answerText.slice(0, 140));
|
|
577
|
-
log(kleur.green(
|
|
587
|
+
log(kleur.green("Auto-reattach succeeded; session marked completed."));
|
|
578
588
|
return true;
|
|
579
589
|
}
|
|
580
590
|
catch (error) {
|
|
@@ -602,7 +612,7 @@ function isPermissionError(error) {
|
|
|
602
612
|
if (!(error instanceof Error))
|
|
603
613
|
return false;
|
|
604
614
|
const code = error.code;
|
|
605
|
-
return code ===
|
|
615
|
+
return code === "EACCES" || code === "EPERM";
|
|
606
616
|
}
|
|
607
617
|
function buildFallbackPath(original) {
|
|
608
618
|
const ext = path.extname(original);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import chalk from
|
|
2
|
-
import kleur from
|
|
3
|
-
import { MODEL_CONFIGS } from
|
|
4
|
-
import { estimateUsdCost } from
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import kleur from "kleur";
|
|
3
|
+
import { MODEL_CONFIGS } from "../oracle.js";
|
|
4
|
+
import { estimateUsdCost } from "tokentally";
|
|
5
5
|
const isRich = (rich) => rich ?? Boolean(process.stdout.isTTY && chalk.level > 0);
|
|
6
6
|
const dim = (text, rich) => (rich ? kleur.dim(text) : text);
|
|
7
7
|
export const STATUS_PAD = 9;
|
|
@@ -11,23 +11,23 @@ export const TIMESTAMP_PAD = 19;
|
|
|
11
11
|
export const CHARS_PAD = 5;
|
|
12
12
|
export const COST_PAD = 7;
|
|
13
13
|
export function formatSessionTableHeader(rich) {
|
|
14
|
-
const header = `${
|
|
14
|
+
const header = `${"Status".padEnd(STATUS_PAD)} ${"Model".padEnd(MODEL_PAD)} ${"Mode".padEnd(MODE_PAD)} ${"Timestamp".padEnd(TIMESTAMP_PAD)} ${"Chars".padStart(CHARS_PAD)} ${"Cost".padStart(COST_PAD)} Slug`;
|
|
15
15
|
return dim(header, isRich(rich));
|
|
16
16
|
}
|
|
17
17
|
export function formatSessionTableRow(meta, options) {
|
|
18
18
|
const rich = isRich(options?.rich);
|
|
19
|
-
const status = colorStatus(meta.status ??
|
|
20
|
-
const modelLabel = (meta.model ??
|
|
19
|
+
const status = colorStatus(meta.status ?? "unknown", rich);
|
|
20
|
+
const modelLabel = (meta.model ?? "n/a").padEnd(MODEL_PAD);
|
|
21
21
|
const model = rich ? chalk.white(modelLabel) : modelLabel;
|
|
22
|
-
const modeLabel = (meta.mode ?? meta.options?.mode ??
|
|
22
|
+
const modeLabel = (meta.mode ?? meta.options?.mode ?? "api").padEnd(MODE_PAD);
|
|
23
23
|
const mode = rich ? chalk.gray(modeLabel) : modeLabel;
|
|
24
24
|
const timestampLabel = formatTimestampAligned(meta.createdAt).padEnd(TIMESTAMP_PAD);
|
|
25
25
|
const timestamp = rich ? chalk.gray(timestampLabel) : timestampLabel;
|
|
26
26
|
const charsValue = meta.options?.prompt?.length ?? meta.promptPreview?.length ?? 0;
|
|
27
|
-
const charsRaw = charsValue > 0 ? String(charsValue).padStart(CHARS_PAD) : `${
|
|
27
|
+
const charsRaw = charsValue > 0 ? String(charsValue).padStart(CHARS_PAD) : `${"".padStart(CHARS_PAD - 1)}-`;
|
|
28
28
|
const chars = rich ? chalk.gray(charsRaw) : charsRaw;
|
|
29
29
|
const costValue = resolveSessionCost(meta);
|
|
30
|
-
const costRaw = costValue != null ? formatCostTable(costValue) : `${
|
|
30
|
+
const costRaw = costValue != null ? formatCostTable(costValue) : `${"".padStart(COST_PAD - 1)}-`;
|
|
31
31
|
const cost = rich ? chalk.gray(costRaw) : costRaw;
|
|
32
32
|
const slugValue = options?.displaySlug ?? meta.id;
|
|
33
33
|
const slug = rich ? chalk.cyan(slugValue) : slugValue;
|
|
@@ -35,7 +35,7 @@ export function formatSessionTableRow(meta, options) {
|
|
|
35
35
|
}
|
|
36
36
|
export function resolveSessionCost(meta) {
|
|
37
37
|
const mode = meta.mode ?? meta.options?.mode;
|
|
38
|
-
if (mode ===
|
|
38
|
+
if (mode === "browser") {
|
|
39
39
|
return null;
|
|
40
40
|
}
|
|
41
41
|
if (meta.usage?.cost != null) {
|
|
@@ -52,25 +52,28 @@ export function resolveSessionCost(meta) {
|
|
|
52
52
|
const output = meta.usage.outputTokens ?? 0;
|
|
53
53
|
const cost = estimateUsdCost({
|
|
54
54
|
usage: { inputTokens: input, outputTokens: output },
|
|
55
|
-
pricing: {
|
|
55
|
+
pricing: {
|
|
56
|
+
inputUsdPerToken: pricing.inputPerToken,
|
|
57
|
+
outputUsdPerToken: pricing.outputPerToken,
|
|
58
|
+
},
|
|
56
59
|
})?.totalUsd ?? 0;
|
|
57
60
|
return cost > 0 ? cost : null;
|
|
58
61
|
}
|
|
59
62
|
export function formatTimestampAligned(iso) {
|
|
60
63
|
const date = new Date(iso);
|
|
61
|
-
const locale =
|
|
64
|
+
const locale = "en-US";
|
|
62
65
|
const opts = {
|
|
63
|
-
year:
|
|
64
|
-
month:
|
|
65
|
-
day:
|
|
66
|
-
hour:
|
|
67
|
-
minute:
|
|
66
|
+
year: "numeric",
|
|
67
|
+
month: "2-digit",
|
|
68
|
+
day: "2-digit",
|
|
69
|
+
hour: "numeric",
|
|
70
|
+
minute: "2-digit",
|
|
68
71
|
second: undefined,
|
|
69
72
|
hour12: true,
|
|
70
73
|
};
|
|
71
74
|
let formatted = date.toLocaleString(locale, opts);
|
|
72
|
-
formatted = formatted.replace(
|
|
73
|
-
return formatted.replace(/(\s)(\d:)/,
|
|
75
|
+
formatted = formatted.replace(", ", " ");
|
|
76
|
+
return formatted.replace(/(\s)(\d:)/, "$1 $2");
|
|
74
77
|
}
|
|
75
78
|
function formatCostTable(cost) {
|
|
76
79
|
return `$${cost.toFixed(3)}`.padStart(COST_PAD);
|
|
@@ -81,11 +84,11 @@ function colorStatus(status, rich) {
|
|
|
81
84
|
return padded;
|
|
82
85
|
}
|
|
83
86
|
switch (status) {
|
|
84
|
-
case
|
|
87
|
+
case "completed":
|
|
85
88
|
return chalk.green(padded);
|
|
86
|
-
case
|
|
89
|
+
case "error":
|
|
87
90
|
return chalk.red(padded);
|
|
88
|
-
case
|
|
91
|
+
case "running":
|
|
89
92
|
return chalk.yellow(padded);
|
|
90
93
|
default:
|
|
91
94
|
return padded;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export async function readStdin(stream = process.stdin) {
|
|
2
|
+
const chunks = [];
|
|
3
|
+
const maybeTextStream = stream;
|
|
4
|
+
maybeTextStream.setEncoding?.("utf8");
|
|
5
|
+
for await (const chunk of stream) {
|
|
6
|
+
chunks.push(typeof chunk === "string" ? chunk : String(chunk));
|
|
7
|
+
}
|
|
8
|
+
return chunks.join("");
|
|
9
|
+
}
|
|
10
|
+
export async function resolveDashPrompt(prompt, stream = process.stdin) {
|
|
11
|
+
if (prompt !== "-") {
|
|
12
|
+
return prompt;
|
|
13
|
+
}
|
|
14
|
+
if (stream.isTTY) {
|
|
15
|
+
throw new Error(`"-p -" requires piped input, for example: echo "prompt" | oracle -p -.`);
|
|
16
|
+
}
|
|
17
|
+
const stdinPrompt = (await readStdin(stream)).trim();
|
|
18
|
+
if (!stdinPrompt) {
|
|
19
|
+
throw new Error(`"-p -" received empty stdin.`);
|
|
20
|
+
}
|
|
21
|
+
return stdinPrompt;
|
|
22
|
+
}
|