@steipete/oracle 0.8.6 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +130 -45
- package/dist/bin/oracle-cli.js +613 -379
- 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 +314 -104
- package/dist/src/browser/actions/navigation.js +161 -136
- 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 +452 -303
- 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 +17 -0
- package/dist/src/browser/providers/chatgptDomProvider.js +49 -0
- package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +254 -0
- package/dist/src/browser/providers/index.js +2 -0
- 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 +65 -45
- 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 +7 -4
- 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 +11 -0
- 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 +12 -8
- package/dist/src/cli/markdownRenderer.js +15 -15
- package/dist/src/cli/notifier.js +77 -67
- package/dist/src/cli/options.js +145 -87
- 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 +37 -25
- package/dist/src/cli/sessionCommand.js +31 -21
- package/dist/src/cli/sessionDisplay.js +182 -79
- package/dist/src/cli/sessionLineage.js +60 -0
- package/dist/src/cli/sessionRunner.js +118 -90
- package/dist/src/cli/sessionTable.js +28 -24
- package/dist/src/cli/stdin.js +22 -0
- package/dist/src/cli/tagline.js +121 -124
- package/dist/src/cli/tui/index.js +140 -127
- package/dist/src/cli/writeOutputPath.js +5 -5
- package/dist/src/config.js +7 -7
- package/dist/src/gemini-web/browserSessionManager.js +80 -0
- package/dist/src/gemini-web/client.js +81 -64
- package/dist/src/gemini-web/executionMode.js +16 -0
- package/dist/src/gemini-web/executor.js +327 -169
- package/dist/src/gemini-web/index.js +1 -1
- package/dist/src/mcp/server.js +16 -12
- package/dist/src/mcp/tools/consult.js +81 -64
- 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 +84 -46
- package/dist/src/oracle/config.js +124 -58
- package/dist/src/oracle/errors.js +38 -38
- package/dist/src/oracle/files.js +69 -45
- package/dist/src/oracle/finishLine.js +10 -8
- package/dist/src/oracle/format.js +3 -3
- package/dist/src/oracle/gemini.js +37 -30
- 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 +23 -15
- package/dist/src/oracle/run.js +172 -140
- 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 +81 -75
- package/dist/src/sessionStore.js +3 -3
- package/dist/src/version.js +10 -10
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/dist/vendor/oracle-notifier/README.md +2 -0
- package/package.json +69 -65
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- 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/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
- /package/dist/{oracle/src/browser/types.js → src/gemini-web/executionClients.js} +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import chalk from
|
|
2
|
-
import kleur from
|
|
3
|
-
import { renderMarkdownAnsi } from
|
|
4
|
-
import { formatFinishLine } from
|
|
5
|
-
import { sessionStore, wait } from
|
|
6
|
-
import { formatTokenCount, formatTokenValue } from
|
|
7
|
-
import { resumeBrowserSession } from
|
|
8
|
-
import { estimateTokenCount } from
|
|
9
|
-
import { formatSessionTableHeader, formatSessionTableRow, resolveSessionCost } from
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import kleur from "kleur";
|
|
3
|
+
import { renderMarkdownAnsi } from "./markdownRenderer.js";
|
|
4
|
+
import { formatFinishLine } from "../oracle/finishLine.js";
|
|
5
|
+
import { sessionStore, wait } from "../sessionStore.js";
|
|
6
|
+
import { formatTokenCount, formatTokenValue } from "../oracle/runUtils.js";
|
|
7
|
+
import { resumeBrowserSession } from "../browser/reattach.js";
|
|
8
|
+
import { estimateTokenCount } from "../browser/utils.js";
|
|
9
|
+
import { formatSessionTableHeader, formatSessionTableRow, resolveSessionCost, } from "./sessionTable.js";
|
|
10
|
+
import { abbreviateResponseId, buildResponseOwnerIndex, resolveSessionLineage, } from "./sessionLineage.js";
|
|
10
11
|
const isTty = () => Boolean(process.stdout.isTTY);
|
|
11
12
|
const dim = (text) => (isTty() ? kleur.dim(text) : text);
|
|
12
13
|
export const MAX_RENDER_BYTES = 200_000;
|
|
@@ -19,10 +20,10 @@ function isProcessAlive(pid) {
|
|
|
19
20
|
}
|
|
20
21
|
catch (error) {
|
|
21
22
|
const code = error instanceof Error ? error.code : undefined;
|
|
22
|
-
if (code ===
|
|
23
|
+
if (code === "ESRCH" || code === "EINVAL") {
|
|
23
24
|
return false;
|
|
24
25
|
}
|
|
25
|
-
if (code ===
|
|
26
|
+
if (code === "EPERM") {
|
|
26
27
|
return true;
|
|
27
28
|
}
|
|
28
29
|
return true;
|
|
@@ -31,9 +32,16 @@ function isProcessAlive(pid) {
|
|
|
31
32
|
const CLEANUP_TIP = 'Tip: Run "oracle session --clear --hours 24" to prune cached runs (add --all to wipe everything).';
|
|
32
33
|
export async function showStatus({ hours, includeAll, limit, showExamples = false, modelFilter, }) {
|
|
33
34
|
const metas = await sessionStore.listSessions();
|
|
34
|
-
const { entries, truncated, total } = sessionStore.filterSessions(metas, {
|
|
35
|
-
|
|
35
|
+
const { entries, truncated, total } = sessionStore.filterSessions(metas, {
|
|
36
|
+
hours,
|
|
37
|
+
includeAll,
|
|
38
|
+
limit,
|
|
39
|
+
});
|
|
40
|
+
const filteredEntries = modelFilter
|
|
41
|
+
? entries.filter((entry) => matchesModel(entry, modelFilter))
|
|
42
|
+
: entries;
|
|
36
43
|
const richTty = process.stdout.isTTY && chalk.level > 0;
|
|
44
|
+
const responseOwners = buildResponseOwnerIndex(metas);
|
|
37
45
|
if (!filteredEntries.length) {
|
|
38
46
|
console.log(CLEANUP_TIP);
|
|
39
47
|
if (showExamples) {
|
|
@@ -41,10 +49,17 @@ export async function showStatus({ hours, includeAll, limit, showExamples = fals
|
|
|
41
49
|
}
|
|
42
50
|
return;
|
|
43
51
|
}
|
|
44
|
-
console.log(chalk.bold(
|
|
52
|
+
console.log(chalk.bold("Recent Sessions"));
|
|
45
53
|
console.log(formatSessionTableHeader(richTty));
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
const treeRows = buildStatusTreeRows(filteredEntries, responseOwners);
|
|
55
|
+
for (const row of treeRows) {
|
|
56
|
+
const line = formatSessionTableRow(row.entry, { rich: richTty, displaySlug: row.displaySlug });
|
|
57
|
+
const detachedParent = row.detachedParentLabel != null
|
|
58
|
+
? richTty
|
|
59
|
+
? chalk.gray(` <- ${row.detachedParentLabel}`)
|
|
60
|
+
: ` <- ${row.detachedParentLabel}`
|
|
61
|
+
: "";
|
|
62
|
+
console.log(`${line}${detachedParent}`);
|
|
48
63
|
}
|
|
49
64
|
if (truncated) {
|
|
50
65
|
const sessionsDir = sessionStore.sessionsDir();
|
|
@@ -61,7 +76,7 @@ export async function attachSession(sessionId, options) {
|
|
|
61
76
|
process.exitCode = 1;
|
|
62
77
|
return;
|
|
63
78
|
}
|
|
64
|
-
if (metadata.mode ===
|
|
79
|
+
if (metadata.mode === "browser" && metadata.status === "running" && !metadata.browser?.runtime) {
|
|
65
80
|
await wait(250);
|
|
66
81
|
const refreshed = await sessionStore.readSession(sessionId);
|
|
67
82
|
if (refreshed) {
|
|
@@ -83,16 +98,16 @@ export async function attachSession(sessionId, options) {
|
|
|
83
98
|
const isVerbose = Boolean(process.env.ORACLE_VERBOSE_RENDER);
|
|
84
99
|
const runtime = metadata.browser?.runtime;
|
|
85
100
|
const controllerAlive = isProcessAlive(runtime?.controllerPid);
|
|
86
|
-
const hasChromeDisconnect = metadata.response?.incompleteReason ===
|
|
87
|
-
const statusAllowsReattach = metadata.status ===
|
|
101
|
+
const hasChromeDisconnect = metadata.response?.incompleteReason === "chrome-disconnected";
|
|
102
|
+
const statusAllowsReattach = metadata.status === "running" || (metadata.status === "error" && hasChromeDisconnect);
|
|
88
103
|
const hasFallbackSessionInfo = Boolean(runtime?.chromePort || runtime?.tabUrl || runtime?.conversationId);
|
|
89
104
|
const canReattach = statusAllowsReattach &&
|
|
90
|
-
metadata.mode ===
|
|
105
|
+
metadata.mode === "browser" &&
|
|
91
106
|
hasFallbackSessionInfo &&
|
|
92
107
|
(hasChromeDisconnect || (runtime?.controllerPid && !controllerAlive));
|
|
93
108
|
if (canReattach) {
|
|
94
|
-
const portInfo = runtime?.chromePort ? `port ${runtime.chromePort}` :
|
|
95
|
-
const urlInfo = runtime?.tabUrl ? `url=${runtime.tabUrl}` :
|
|
109
|
+
const portInfo = runtime?.chromePort ? `port ${runtime.chromePort}` : "unknown port";
|
|
110
|
+
const urlInfo = runtime?.tabUrl ? `url=${runtime.tabUrl}` : "url=unknown";
|
|
96
111
|
console.log(chalk.yellow(`Attempting to reattach to the existing Chrome session (${portInfo}, ${urlInfo})...`));
|
|
97
112
|
try {
|
|
98
113
|
const result = await resumeBrowserSession(runtime, metadata.browser?.config, Object.assign(((message) => {
|
|
@@ -102,13 +117,13 @@ export async function attachSession(sessionId, options) {
|
|
|
102
117
|
}), { verbose: true }), { promptPreview: metadata.promptPreview });
|
|
103
118
|
const outputTokens = estimateTokenCount(result.answerMarkdown);
|
|
104
119
|
const logWriter = sessionStore.createLogWriter(sessionId);
|
|
105
|
-
logWriter.logLine(
|
|
106
|
-
logWriter.logLine(
|
|
120
|
+
logWriter.logLine("[reattach] captured assistant response from existing Chrome tab");
|
|
121
|
+
logWriter.logLine("Answer:");
|
|
107
122
|
logWriter.logLine(result.answerMarkdown || result.answerText);
|
|
108
123
|
logWriter.stream.end();
|
|
109
124
|
if (metadata.model) {
|
|
110
125
|
await sessionStore.updateModelRun(metadata.id, metadata.model, {
|
|
111
|
-
status:
|
|
126
|
+
status: "completed",
|
|
112
127
|
usage: {
|
|
113
128
|
inputTokens: 0,
|
|
114
129
|
outputTokens,
|
|
@@ -119,7 +134,7 @@ export async function attachSession(sessionId, options) {
|
|
|
119
134
|
});
|
|
120
135
|
}
|
|
121
136
|
await sessionStore.updateSession(sessionId, {
|
|
122
|
-
status:
|
|
137
|
+
status: "completed",
|
|
123
138
|
completedAt: new Date().toISOString(),
|
|
124
139
|
usage: {
|
|
125
140
|
inputTokens: 0,
|
|
@@ -131,11 +146,11 @@ export async function attachSession(sessionId, options) {
|
|
|
131
146
|
config: metadata.browser?.config,
|
|
132
147
|
runtime,
|
|
133
148
|
},
|
|
134
|
-
response: { status:
|
|
149
|
+
response: { status: "completed" },
|
|
135
150
|
error: undefined,
|
|
136
151
|
transport: undefined,
|
|
137
152
|
});
|
|
138
|
-
console.log(chalk.green(
|
|
153
|
+
console.log(chalk.green("Reattach succeeded; session marked completed."));
|
|
139
154
|
metadata = (await sessionStore.readSession(sessionId)) ?? metadata;
|
|
140
155
|
}
|
|
141
156
|
catch (error) {
|
|
@@ -148,14 +163,18 @@ export async function attachSession(sessionId, options) {
|
|
|
148
163
|
if (reattachLine) {
|
|
149
164
|
console.log(chalk.blue(reattachLine));
|
|
150
165
|
}
|
|
166
|
+
const chainLine = await buildSessionChainLine(metadata);
|
|
167
|
+
if (chainLine) {
|
|
168
|
+
console.log(dim(`Chain: ${chainLine}`));
|
|
169
|
+
}
|
|
151
170
|
console.log(`Created: ${metadata.createdAt}`);
|
|
152
171
|
console.log(`Status: ${metadata.status}`);
|
|
153
172
|
if (metadata.models && metadata.models.length > 0) {
|
|
154
|
-
console.log(
|
|
173
|
+
console.log("Models:");
|
|
155
174
|
for (const run of metadata.models) {
|
|
156
175
|
const usage = run.usage
|
|
157
176
|
? ` tok=${formatTokenCount(run.usage.outputTokens ?? 0)}/${formatTokenCount(run.usage.totalTokens ?? 0)}`
|
|
158
|
-
:
|
|
177
|
+
: "";
|
|
159
178
|
console.log(`- ${chalk.cyan(run.model)} — ${run.status}${usage}`);
|
|
160
179
|
}
|
|
161
180
|
}
|
|
@@ -175,19 +194,19 @@ export async function attachSession(sessionId, options) {
|
|
|
175
194
|
console.log(dim(`User error: ${userErrorSummary}`));
|
|
176
195
|
}
|
|
177
196
|
}
|
|
178
|
-
const shouldTrimIntro = initialStatus ===
|
|
197
|
+
const shouldTrimIntro = initialStatus === "completed" || initialStatus === "error";
|
|
179
198
|
if (options?.renderPrompt !== false) {
|
|
180
199
|
const prompt = await readStoredPrompt(sessionId);
|
|
181
200
|
if (prompt) {
|
|
182
|
-
console.log(chalk.bold(
|
|
201
|
+
console.log(chalk.bold("Prompt:"));
|
|
183
202
|
console.log(renderMarkdownAnsi(prompt));
|
|
184
|
-
console.log(dim(
|
|
203
|
+
console.log(dim("---"));
|
|
185
204
|
}
|
|
186
205
|
}
|
|
187
206
|
if (shouldTrimIntro) {
|
|
188
207
|
const fullLog = await buildSessionLogForDisplay(sessionId, metadata, normalizedModelFilter);
|
|
189
208
|
const trimmed = trimBeforeFirstAnswer(fullLog);
|
|
190
|
-
const size = Buffer.byteLength(trimmed,
|
|
209
|
+
const size = Buffer.byteLength(trimmed, "utf8");
|
|
191
210
|
const canRender = wantsRender && isTty() && size <= MAX_RENDER_BYTES;
|
|
192
211
|
if (wantsRender && size > MAX_RENDER_BYTES) {
|
|
193
212
|
const msg = `Render skipped (log too large: ${size} bytes > ${MAX_RENDER_BYTES}). Showing raw text.`;
|
|
@@ -197,7 +216,7 @@ export async function attachSession(sessionId, options) {
|
|
|
197
216
|
}
|
|
198
217
|
}
|
|
199
218
|
else if (wantsRender && !isTty()) {
|
|
200
|
-
const msg =
|
|
219
|
+
const msg = "Render requested but stdout is not a TTY; showing raw text.";
|
|
201
220
|
console.log(dim(msg));
|
|
202
221
|
if (isVerbose) {
|
|
203
222
|
console.log(dim(`Verbose: renderMarkdown=true tty=${isTty()} size=${size}`));
|
|
@@ -219,13 +238,20 @@ export async function attachSession(sessionId, options) {
|
|
|
219
238
|
return;
|
|
220
239
|
}
|
|
221
240
|
if (wantsRender) {
|
|
222
|
-
console.log(dim(
|
|
241
|
+
console.log(dim("Render will apply after completion; streaming raw text meanwhile..."));
|
|
223
242
|
if (isVerbose) {
|
|
224
243
|
console.log(dim(`Verbose: streaming phase renderMarkdown=true tty=${isTty()}`));
|
|
225
244
|
}
|
|
226
245
|
}
|
|
227
246
|
const liveRenderState = wantsRender && isTty()
|
|
228
|
-
? {
|
|
247
|
+
? {
|
|
248
|
+
pending: "",
|
|
249
|
+
inFence: false,
|
|
250
|
+
inTable: false,
|
|
251
|
+
renderedBytes: 0,
|
|
252
|
+
fallback: false,
|
|
253
|
+
noticedFallback: false,
|
|
254
|
+
}
|
|
229
255
|
: null;
|
|
230
256
|
let lastLength = 0;
|
|
231
257
|
const renderLiveChunk = (chunk) => {
|
|
@@ -241,7 +267,7 @@ export async function attachSession(sessionId, options) {
|
|
|
241
267
|
const { chunks, remainder } = extractRenderableChunks(liveRenderState.pending, liveRenderState);
|
|
242
268
|
liveRenderState.pending = remainder;
|
|
243
269
|
for (const candidate of chunks) {
|
|
244
|
-
const projected = liveRenderState.renderedBytes + Buffer.byteLength(candidate,
|
|
270
|
+
const projected = liveRenderState.renderedBytes + Buffer.byteLength(candidate, "utf8");
|
|
245
271
|
if (projected > MAX_RENDER_BYTES) {
|
|
246
272
|
if (!liveRenderState.noticedFallback) {
|
|
247
273
|
console.log(dim(`Render skipped (log too large: > ${MAX_RENDER_BYTES} bytes). Showing raw text.`));
|
|
@@ -249,11 +275,11 @@ export async function attachSession(sessionId, options) {
|
|
|
249
275
|
}
|
|
250
276
|
liveRenderState.fallback = true;
|
|
251
277
|
process.stdout.write(candidate + liveRenderState.pending);
|
|
252
|
-
liveRenderState.pending =
|
|
278
|
+
liveRenderState.pending = "";
|
|
253
279
|
return;
|
|
254
280
|
}
|
|
255
281
|
process.stdout.write(renderMarkdownAnsi(candidate));
|
|
256
|
-
liveRenderState.renderedBytes += Buffer.byteLength(candidate,
|
|
282
|
+
liveRenderState.renderedBytes += Buffer.byteLength(candidate, "utf8");
|
|
257
283
|
}
|
|
258
284
|
};
|
|
259
285
|
const flushRemainder = () => {
|
|
@@ -264,8 +290,8 @@ export async function attachSession(sessionId, options) {
|
|
|
264
290
|
return;
|
|
265
291
|
}
|
|
266
292
|
const text = liveRenderState.pending;
|
|
267
|
-
liveRenderState.pending =
|
|
268
|
-
const projected = liveRenderState.renderedBytes + Buffer.byteLength(text,
|
|
293
|
+
liveRenderState.pending = "";
|
|
294
|
+
const projected = liveRenderState.renderedBytes + Buffer.byteLength(text, "utf8");
|
|
269
295
|
if (projected > MAX_RENDER_BYTES) {
|
|
270
296
|
if (!liveRenderState.noticedFallback) {
|
|
271
297
|
console.log(dim(`Render skipped (log too large: > ${MAX_RENDER_BYTES} bytes). Showing raw text.`));
|
|
@@ -291,15 +317,15 @@ export async function attachSession(sessionId, options) {
|
|
|
291
317
|
if (!latest) {
|
|
292
318
|
break;
|
|
293
319
|
}
|
|
294
|
-
if (latest.status ===
|
|
320
|
+
if (latest.status === "completed" || latest.status === "error") {
|
|
295
321
|
await printNew();
|
|
296
322
|
flushRemainder();
|
|
297
323
|
if (!options?.suppressMetadata) {
|
|
298
|
-
if (latest.status ===
|
|
299
|
-
console.log(
|
|
324
|
+
if (latest.status === "error" && latest.errorMessage) {
|
|
325
|
+
console.log("\nResult:");
|
|
300
326
|
console.log(`Session failed: ${latest.errorMessage}`);
|
|
301
327
|
}
|
|
302
|
-
if (latest.status ===
|
|
328
|
+
if (latest.status === "completed" && latest.usage) {
|
|
303
329
|
const summary = formatCompletionSummary(latest, { includeSlug: true });
|
|
304
330
|
if (summary) {
|
|
305
331
|
console.log(`\n${chalk.green.bold(summary)}`);
|
|
@@ -333,19 +359,19 @@ export function formatResponseMetadata(metadata) {
|
|
|
333
359
|
if (metadata.incompleteReason) {
|
|
334
360
|
parts.push(`incomplete=${metadata.incompleteReason}`);
|
|
335
361
|
}
|
|
336
|
-
return parts.length > 0 ? parts.join(
|
|
362
|
+
return parts.length > 0 ? parts.join(" | ") : null;
|
|
337
363
|
}
|
|
338
364
|
export function formatTransportMetadata(metadata) {
|
|
339
365
|
if (!metadata?.reason) {
|
|
340
366
|
return null;
|
|
341
367
|
}
|
|
342
368
|
const reasonLabels = {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
unknown:
|
|
369
|
+
"client-timeout": "client timeout (deadline exceeded)",
|
|
370
|
+
"connection-lost": "connection lost before completion",
|
|
371
|
+
"client-abort": "request aborted locally",
|
|
372
|
+
unknown: "unknown transport failure",
|
|
347
373
|
};
|
|
348
|
-
const label = reasonLabels[metadata.reason] ??
|
|
374
|
+
const label = reasonLabels[metadata.reason] ?? "transport error";
|
|
349
375
|
return `${metadata.reason} — ${label}`;
|
|
350
376
|
}
|
|
351
377
|
export function formatUserErrorMetadata(metadata) {
|
|
@@ -362,7 +388,7 @@ export function formatUserErrorMetadata(metadata) {
|
|
|
362
388
|
if (metadata.details && Object.keys(metadata.details).length > 0) {
|
|
363
389
|
parts.push(`details=${JSON.stringify(metadata.details)}`);
|
|
364
390
|
}
|
|
365
|
-
return parts.length > 0 ? parts.join(
|
|
391
|
+
return parts.length > 0 ? parts.join(" | ") : null;
|
|
366
392
|
}
|
|
367
393
|
export function buildReattachLine(metadata) {
|
|
368
394
|
if (!metadata.id) {
|
|
@@ -376,13 +402,13 @@ export function buildReattachLine(metadata) {
|
|
|
376
402
|
if (!elapsedLabel) {
|
|
377
403
|
return null;
|
|
378
404
|
}
|
|
379
|
-
if (metadata.status ===
|
|
405
|
+
if (metadata.status === "running") {
|
|
380
406
|
return `Session ${metadata.id} reattached, request started ${elapsedLabel} ago.`;
|
|
381
407
|
}
|
|
382
408
|
return null;
|
|
383
409
|
}
|
|
384
410
|
export function trimBeforeFirstAnswer(logText) {
|
|
385
|
-
const marker =
|
|
411
|
+
const marker = "Answer:";
|
|
386
412
|
const index = logText.indexOf(marker);
|
|
387
413
|
if (index === -1) {
|
|
388
414
|
return logText;
|
|
@@ -414,7 +440,7 @@ function formatRelativeDuration(referenceIso) {
|
|
|
414
440
|
if (remainingMinutes > 0) {
|
|
415
441
|
parts.push(`${remainingMinutes}m`);
|
|
416
442
|
}
|
|
417
|
-
return parts.join(
|
|
443
|
+
return parts.join(" ");
|
|
418
444
|
}
|
|
419
445
|
const days = Math.floor(hours / 24);
|
|
420
446
|
const remainingHours = hours % 24;
|
|
@@ -425,17 +451,17 @@ function formatRelativeDuration(referenceIso) {
|
|
|
425
451
|
if (remainingMinutes > 0 && days === 0) {
|
|
426
452
|
parts.push(`${remainingMinutes}m`);
|
|
427
453
|
}
|
|
428
|
-
return parts.join(
|
|
454
|
+
return parts.join(" ");
|
|
429
455
|
}
|
|
430
456
|
function printStatusExamples() {
|
|
431
|
-
console.log(
|
|
432
|
-
console.log(chalk.bold(
|
|
433
|
-
console.log(`${chalk.bold(
|
|
434
|
-
console.log(dim(
|
|
435
|
-
console.log(`${chalk.bold(
|
|
436
|
-
console.log(dim(
|
|
437
|
-
console.log(`${chalk.bold(
|
|
438
|
-
console.log(dim(
|
|
457
|
+
console.log("");
|
|
458
|
+
console.log(chalk.bold("Usage Examples"));
|
|
459
|
+
console.log(`${chalk.bold(" oracle status --hours 72 --limit 50")}`);
|
|
460
|
+
console.log(dim(" Show 72h of history capped at 50 entries."));
|
|
461
|
+
console.log(`${chalk.bold(" oracle status --clear --hours 168")}`);
|
|
462
|
+
console.log(dim(" Delete sessions older than 7 days (use --all to wipe everything)."));
|
|
463
|
+
console.log(`${chalk.bold(" oracle session <session-id>")}`);
|
|
464
|
+
console.log(dim(" Attach to a specific running/completed session to stream its output."));
|
|
439
465
|
console.log(dim(CLEANUP_TIP));
|
|
440
466
|
}
|
|
441
467
|
function matchesModel(entry, filter) {
|
|
@@ -443,9 +469,86 @@ function matchesModel(entry, filter) {
|
|
|
443
469
|
if (!normalized) {
|
|
444
470
|
return true;
|
|
445
471
|
}
|
|
446
|
-
const models = entry.models?.map((model) => model.model.toLowerCase()) ??
|
|
472
|
+
const models = entry.models?.map((model) => model.model.toLowerCase()) ??
|
|
473
|
+
(entry.model ? [entry.model.toLowerCase()] : []);
|
|
447
474
|
return models.includes(normalized);
|
|
448
475
|
}
|
|
476
|
+
function buildStatusTreeRows(entries, responseOwners) {
|
|
477
|
+
const entryById = new Map(entries.map((entry) => [entry.id, entry]));
|
|
478
|
+
const orderIndex = new Map(entries.map((entry, index) => [entry.id, index]));
|
|
479
|
+
const lineageById = new Map();
|
|
480
|
+
const childMap = new Map();
|
|
481
|
+
for (const entry of entries) {
|
|
482
|
+
const lineage = resolveSessionLineage(entry, responseOwners);
|
|
483
|
+
lineageById.set(entry.id, lineage);
|
|
484
|
+
const parentId = lineage?.parentSessionId;
|
|
485
|
+
if (parentId && parentId !== entry.id && entryById.has(parentId)) {
|
|
486
|
+
const siblings = childMap.get(parentId) ?? [];
|
|
487
|
+
siblings.push(entry);
|
|
488
|
+
childMap.set(parentId, siblings);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
for (const siblings of childMap.values()) {
|
|
492
|
+
siblings.sort((a, b) => (orderIndex.get(a.id) ?? 0) - (orderIndex.get(b.id) ?? 0));
|
|
493
|
+
}
|
|
494
|
+
const rows = [];
|
|
495
|
+
const visited = new Set();
|
|
496
|
+
const walkChild = (entry, ancestorHasMore, isLast) => {
|
|
497
|
+
if (visited.has(entry.id)) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
visited.add(entry.id);
|
|
501
|
+
const children = childMap.get(entry.id) ?? [];
|
|
502
|
+
const nodeBranch = isLast ? "└─ " : "├─ ";
|
|
503
|
+
const prefix = `${ancestorHasMore.map((hasMore) => (hasMore ? "│ " : " ")).join("")}${nodeBranch}`;
|
|
504
|
+
rows.push({ entry, displaySlug: `${prefix}${entry.id}` });
|
|
505
|
+
children.forEach((child, index) => {
|
|
506
|
+
walkChild(child, [...ancestorHasMore, !isLast], index === children.length - 1);
|
|
507
|
+
});
|
|
508
|
+
};
|
|
509
|
+
const walkRoot = (entry) => {
|
|
510
|
+
if (visited.has(entry.id)) {
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
visited.add(entry.id);
|
|
514
|
+
const lineage = lineageById.get(entry.id);
|
|
515
|
+
const hiddenParent = lineage?.parentSessionId && !entryById.has(lineage.parentSessionId)
|
|
516
|
+
? `${lineage.parentSessionId} (${abbreviateResponseId(lineage.parentResponseId)})`
|
|
517
|
+
: undefined;
|
|
518
|
+
const children = childMap.get(entry.id) ?? [];
|
|
519
|
+
rows.push({ entry, displaySlug: entry.id, detachedParentLabel: hiddenParent });
|
|
520
|
+
children.forEach((child, index) => {
|
|
521
|
+
walkChild(child, [], index === children.length - 1);
|
|
522
|
+
});
|
|
523
|
+
};
|
|
524
|
+
const roots = entries.filter((entry) => {
|
|
525
|
+
const parentId = lineageById.get(entry.id)?.parentSessionId;
|
|
526
|
+
return !(parentId && parentId !== entry.id && entryById.has(parentId));
|
|
527
|
+
});
|
|
528
|
+
roots.forEach((entry) => {
|
|
529
|
+
walkRoot(entry);
|
|
530
|
+
});
|
|
531
|
+
entries.forEach((entry) => {
|
|
532
|
+
walkRoot(entry);
|
|
533
|
+
});
|
|
534
|
+
return rows;
|
|
535
|
+
}
|
|
536
|
+
async function buildSessionChainLine(metadata) {
|
|
537
|
+
const lineageWithoutLookup = resolveSessionLineage(metadata);
|
|
538
|
+
if (!lineageWithoutLookup) {
|
|
539
|
+
return `root -> ${metadata.id}`;
|
|
540
|
+
}
|
|
541
|
+
if (lineageWithoutLookup.parentSessionId) {
|
|
542
|
+
return `${lineageWithoutLookup.parentSessionId} (${abbreviateResponseId(lineageWithoutLookup.parentResponseId)}) -> ${metadata.id}`;
|
|
543
|
+
}
|
|
544
|
+
const sessions = await sessionStore.listSessions().catch(() => []);
|
|
545
|
+
const responseOwners = buildResponseOwnerIndex(sessions);
|
|
546
|
+
const lineage = resolveSessionLineage(metadata, responseOwners) ?? lineageWithoutLookup;
|
|
547
|
+
if (lineage.parentSessionId) {
|
|
548
|
+
return `${lineage.parentSessionId} (${abbreviateResponseId(lineage.parentResponseId)}) -> ${metadata.id}`;
|
|
549
|
+
}
|
|
550
|
+
return `${abbreviateResponseId(lineage.parentResponseId)} -> ${metadata.id}`;
|
|
551
|
+
}
|
|
449
552
|
async function buildSessionLogForDisplay(sessionId, fallbackMeta, modelFilter) {
|
|
450
553
|
const normalizedFilter = modelFilter?.trim().toLowerCase();
|
|
451
554
|
const freshMetadata = (await sessionStore.readSession(sessionId)) ?? fallbackMeta;
|
|
@@ -460,12 +563,12 @@ async function buildSessionLogForDisplay(sessionId, fallbackMeta, modelFilter) {
|
|
|
460
563
|
? models.filter((model) => model.model.toLowerCase() === normalizedFilter)
|
|
461
564
|
: models;
|
|
462
565
|
if (candidates.length === 0) {
|
|
463
|
-
return
|
|
566
|
+
return "";
|
|
464
567
|
}
|
|
465
568
|
const sections = [];
|
|
466
569
|
let hasContent = false;
|
|
467
570
|
for (const model of candidates) {
|
|
468
|
-
const body = (await sessionStore.readModelLog(sessionId, model.model)) ??
|
|
571
|
+
const body = (await sessionStore.readModelLog(sessionId, model.model)) ?? "";
|
|
469
572
|
if (body.trim().length > 0) {
|
|
470
573
|
hasContent = true;
|
|
471
574
|
}
|
|
@@ -475,18 +578,18 @@ async function buildSessionLogForDisplay(sessionId, fallbackMeta, modelFilter) {
|
|
|
475
578
|
// Fallback for runs that recorded output only in the session log (e.g., browser runs without per-model logs).
|
|
476
579
|
return await sessionStore.readLog(sessionId);
|
|
477
580
|
}
|
|
478
|
-
return sections.join(
|
|
581
|
+
return sections.join("\n\n");
|
|
479
582
|
}
|
|
480
583
|
function extractRenderableChunks(text, state) {
|
|
481
584
|
const chunks = [];
|
|
482
|
-
let buffer =
|
|
585
|
+
let buffer = "";
|
|
483
586
|
const lines = text.split(/(\n)/);
|
|
484
587
|
for (let i = 0; i < lines.length; i += 1) {
|
|
485
588
|
const segment = lines[i];
|
|
486
|
-
if (segment ===
|
|
589
|
+
if (segment === "\n") {
|
|
487
590
|
buffer += segment;
|
|
488
591
|
// Detect code fences
|
|
489
|
-
const prev = lines[i - 1] ??
|
|
592
|
+
const prev = lines[i - 1] ?? "";
|
|
490
593
|
const fenceMatch = prev.match(/^(\s*)(`{3,}|~{3,})(.*)$/);
|
|
491
594
|
if (!state.inFence && fenceMatch) {
|
|
492
595
|
state.inFence = true;
|
|
@@ -498,17 +601,17 @@ function extractRenderableChunks(text, state) {
|
|
|
498
601
|
}
|
|
499
602
|
const trimmed = prev.trim();
|
|
500
603
|
if (!state.inFence) {
|
|
501
|
-
if (!state.inTable && trimmed.startsWith(
|
|
604
|
+
if (!state.inTable && trimmed.startsWith("|") && trimmed.includes("|")) {
|
|
502
605
|
state.inTable = true;
|
|
503
606
|
}
|
|
504
|
-
if (state.inTable && trimmed ===
|
|
607
|
+
if (state.inTable && trimmed === "") {
|
|
505
608
|
state.inTable = false;
|
|
506
609
|
}
|
|
507
610
|
}
|
|
508
|
-
const safeBreak = !state.inFence && !state.inTable && trimmed ===
|
|
611
|
+
const safeBreak = !state.inFence && !state.inTable && trimmed === "";
|
|
509
612
|
if (safeBreak) {
|
|
510
613
|
chunks.push(buffer);
|
|
511
|
-
buffer =
|
|
614
|
+
buffer = "";
|
|
512
615
|
}
|
|
513
616
|
continue;
|
|
514
617
|
}
|
|
@@ -520,7 +623,7 @@ export function formatCompletionSummary(metadata, options = {}) {
|
|
|
520
623
|
if (!metadata.usage || metadata.elapsedMs == null) {
|
|
521
624
|
return null;
|
|
522
625
|
}
|
|
523
|
-
const modeLabel = metadata.mode ===
|
|
626
|
+
const modeLabel = metadata.mode === "browser" ? `${metadata.model ?? "n/a"}[browser]` : (metadata.model ?? "n/a");
|
|
524
627
|
const usage = metadata.usage;
|
|
525
628
|
const cost = resolveSessionCost(metadata);
|
|
526
629
|
const tokensDisplay = [
|
|
@@ -535,9 +638,9 @@ export function formatCompletionSummary(metadata, options = {}) {
|
|
|
535
638
|
reasoning_tokens: usage.reasoningTokens,
|
|
536
639
|
total_tokens: usage.totalTokens,
|
|
537
640
|
}, index))
|
|
538
|
-
.join(
|
|
641
|
+
.join("/");
|
|
539
642
|
const tokensPart = (() => {
|
|
540
|
-
const parts = tokensDisplay.split(
|
|
643
|
+
const parts = tokensDisplay.split("/");
|
|
541
644
|
if (parts.length !== 4)
|
|
542
645
|
return tokensDisplay;
|
|
543
646
|
return `↑${parts[0]} ↓${parts[1]} ↻${parts[2]} Δ${parts[3]}`;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
function readResponseId(record) {
|
|
2
|
+
if (!record)
|
|
3
|
+
return null;
|
|
4
|
+
const candidate = typeof record.responseId === "string"
|
|
5
|
+
? record.responseId
|
|
6
|
+
: typeof record.id === "string"
|
|
7
|
+
? record.id
|
|
8
|
+
: null;
|
|
9
|
+
if (!candidate || !candidate.startsWith("resp_")) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
return candidate;
|
|
13
|
+
}
|
|
14
|
+
export function collectSessionResponseIds(meta) {
|
|
15
|
+
const ids = new Set();
|
|
16
|
+
const rootResponse = readResponseId(meta.response);
|
|
17
|
+
if (rootResponse) {
|
|
18
|
+
ids.add(rootResponse);
|
|
19
|
+
}
|
|
20
|
+
const runs = Array.isArray(meta.models) ? meta.models : [];
|
|
21
|
+
for (const run of runs) {
|
|
22
|
+
const runResponse = readResponseId(run.response);
|
|
23
|
+
if (runResponse) {
|
|
24
|
+
ids.add(runResponse);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return [...ids];
|
|
28
|
+
}
|
|
29
|
+
export function buildResponseOwnerIndex(sessions) {
|
|
30
|
+
const byResponse = new Map();
|
|
31
|
+
for (const session of sessions) {
|
|
32
|
+
for (const responseId of collectSessionResponseIds(session)) {
|
|
33
|
+
if (!byResponse.has(responseId)) {
|
|
34
|
+
byResponse.set(responseId, session.id);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return byResponse;
|
|
39
|
+
}
|
|
40
|
+
export function resolveSessionLineage(meta, responseOwners) {
|
|
41
|
+
const previous = meta.options?.previousResponseId?.trim();
|
|
42
|
+
if (!previous) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
let parentSessionId = meta.options?.followupSessionId?.trim();
|
|
46
|
+
if (!parentSessionId && responseOwners) {
|
|
47
|
+
parentSessionId = responseOwners.get(previous);
|
|
48
|
+
}
|
|
49
|
+
if (parentSessionId === meta.id) {
|
|
50
|
+
parentSessionId = undefined;
|
|
51
|
+
}
|
|
52
|
+
return { parentResponseId: previous, parentSessionId };
|
|
53
|
+
}
|
|
54
|
+
export function abbreviateResponseId(responseId, max = 18) {
|
|
55
|
+
if (responseId.length <= max) {
|
|
56
|
+
return responseId;
|
|
57
|
+
}
|
|
58
|
+
const head = Math.max(8, max - 7);
|
|
59
|
+
return `${responseId.slice(0, head)}...${responseId.slice(-4)}`;
|
|
60
|
+
}
|