@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.
Files changed (177) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +61 -48
  3. package/dist/bin/oracle-cli.js +455 -402
  4. package/dist/bin/oracle-mcp.js +2 -2
  5. package/dist/bin/oracle.js +165 -279
  6. package/dist/scripts/agent-send.js +31 -31
  7. package/dist/scripts/check.js +6 -6
  8. package/dist/scripts/debug/extract-chatgpt-response.js +10 -10
  9. package/dist/scripts/docs-list.js +30 -30
  10. package/dist/scripts/git-policy.js +25 -23
  11. package/dist/scripts/run-cli.js +8 -8
  12. package/dist/scripts/runner.js +203 -195
  13. package/dist/scripts/test-browser.js +21 -18
  14. package/dist/scripts/test-remote-chrome.js +20 -20
  15. package/dist/src/bridge/connection.js +18 -18
  16. package/dist/src/bridge/userConfigFile.js +7 -7
  17. package/dist/src/browser/actions/assistantResponse.js +149 -101
  18. package/dist/src/browser/actions/attachmentDataTransfer.js +49 -47
  19. package/dist/src/browser/actions/attachments.js +246 -150
  20. package/dist/src/browser/actions/domEvents.js +2 -2
  21. package/dist/src/browser/actions/modelSelection.js +275 -117
  22. package/dist/src/browser/actions/navigation.js +161 -137
  23. package/dist/src/browser/actions/promptComposer.js +100 -64
  24. package/dist/src/browser/actions/remoteFileTransfer.js +10 -10
  25. package/dist/src/browser/actions/thinkingTime.js +207 -110
  26. package/dist/src/browser/chromeLifecycle.js +62 -60
  27. package/dist/src/browser/config.js +34 -15
  28. package/dist/src/browser/constants.js +17 -12
  29. package/dist/src/browser/cookies.js +19 -19
  30. package/dist/src/browser/detect.js +62 -62
  31. package/dist/src/browser/domDebug.js +1 -1
  32. package/dist/src/browser/index.js +390 -295
  33. package/dist/src/browser/modelStrategy.js +1 -1
  34. package/dist/src/browser/pageActions.js +5 -5
  35. package/dist/src/browser/policies.js +16 -13
  36. package/dist/src/browser/profileState.js +44 -39
  37. package/dist/src/browser/prompt.js +72 -42
  38. package/dist/src/browser/promptSummary.js +5 -5
  39. package/dist/src/browser/providerDomFlow.js +1 -1
  40. package/dist/src/browser/providers/chatgptDomProvider.js +9 -9
  41. package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +51 -42
  42. package/dist/src/browser/providers/index.js +2 -2
  43. package/dist/src/browser/reattach.js +67 -34
  44. package/dist/src/browser/reattachHelpers.js +31 -26
  45. package/dist/src/browser/sessionRunner.js +37 -25
  46. package/dist/src/browser/utils.js +9 -9
  47. package/dist/src/browserMode.js +1 -1
  48. package/dist/src/cli/bridge/claudeConfig.js +16 -16
  49. package/dist/src/cli/bridge/client.js +28 -20
  50. package/dist/src/cli/bridge/codexConfig.js +16 -16
  51. package/dist/src/cli/bridge/doctor.js +47 -39
  52. package/dist/src/cli/bridge/host.js +58 -56
  53. package/dist/src/cli/browserConfig.js +62 -48
  54. package/dist/src/cli/browserDefaults.js +27 -26
  55. package/dist/src/cli/bundleWarnings.js +1 -1
  56. package/dist/src/cli/clipboard.js +11 -2
  57. package/dist/src/cli/detach.js +2 -2
  58. package/dist/src/cli/dryRun.js +29 -25
  59. package/dist/src/cli/duplicatePromptGuard.js +3 -3
  60. package/dist/src/cli/engine.js +9 -9
  61. package/dist/src/cli/errorUtils.js +1 -1
  62. package/dist/src/cli/fileSize.js +3 -3
  63. package/dist/src/cli/format.js +2 -2
  64. package/dist/src/cli/help.js +28 -28
  65. package/dist/src/cli/hiddenAliases.js +3 -3
  66. package/dist/src/cli/markdownBundle.js +7 -7
  67. package/dist/src/cli/markdownRenderer.js +15 -15
  68. package/dist/src/cli/notifier.js +77 -67
  69. package/dist/src/cli/options.js +127 -106
  70. package/dist/src/cli/oscUtils.js +1 -1
  71. package/dist/src/cli/promptRequirement.js +2 -2
  72. package/dist/src/cli/renderOutput.js +1 -1
  73. package/dist/src/cli/rootAlias.js +1 -1
  74. package/dist/src/cli/runOptions.js +32 -28
  75. package/dist/src/cli/sessionCommand.js +31 -21
  76. package/dist/src/cli/sessionDisplay.js +95 -81
  77. package/dist/src/cli/sessionLineage.js +6 -2
  78. package/dist/src/cli/sessionRunner.js +103 -93
  79. package/dist/src/cli/sessionTable.js +26 -23
  80. package/dist/src/cli/stdin.js +22 -0
  81. package/dist/src/cli/tagline.js +121 -124
  82. package/dist/src/cli/tui/index.js +139 -128
  83. package/dist/src/cli/writeOutputPath.js +5 -5
  84. package/dist/src/config.js +7 -7
  85. package/dist/src/gemini-web/browserSessionManager.js +19 -15
  86. package/dist/src/gemini-web/client.js +76 -70
  87. package/dist/src/gemini-web/executionMode.js +6 -8
  88. package/dist/src/gemini-web/executor.js +98 -93
  89. package/dist/src/gemini-web/index.js +1 -1
  90. package/dist/src/mcp/server.js +16 -12
  91. package/dist/src/mcp/tools/consult.js +51 -47
  92. package/dist/src/mcp/tools/sessionResources.js +12 -12
  93. package/dist/src/mcp/tools/sessions.js +26 -17
  94. package/dist/src/mcp/types.js +5 -5
  95. package/dist/src/mcp/utils.js +15 -7
  96. package/dist/src/oracle/background.js +15 -15
  97. package/dist/src/oracle/claude.js +53 -25
  98. package/dist/src/oracle/client.js +50 -41
  99. package/dist/src/oracle/config.js +96 -66
  100. package/dist/src/oracle/errors.js +38 -38
  101. package/dist/src/oracle/files.js +55 -46
  102. package/dist/src/oracle/finishLine.js +10 -8
  103. package/dist/src/oracle/format.js +3 -3
  104. package/dist/src/oracle/gemini.js +37 -33
  105. package/dist/src/oracle/logging.js +7 -7
  106. package/dist/src/oracle/markdown.js +28 -28
  107. package/dist/src/oracle/modelResolver.js +16 -16
  108. package/dist/src/oracle/multiModelRunner.js +12 -12
  109. package/dist/src/oracle/oscProgress.js +8 -8
  110. package/dist/src/oracle/promptAssembly.js +6 -3
  111. package/dist/src/oracle/request.js +16 -13
  112. package/dist/src/oracle/run.js +156 -134
  113. package/dist/src/oracle/runUtils.js +8 -5
  114. package/dist/src/oracle/tokenEstimate.js +6 -6
  115. package/dist/src/oracle/tokenStats.js +5 -5
  116. package/dist/src/oracle/tokenStringifier.js +5 -5
  117. package/dist/src/oracle.js +12 -12
  118. package/dist/src/oracleHome.js +3 -3
  119. package/dist/src/remote/client.js +25 -25
  120. package/dist/src/remote/health.js +20 -20
  121. package/dist/src/remote/remoteServiceConfig.js +9 -9
  122. package/dist/src/remote/server.js +129 -118
  123. package/dist/src/sessionManager.js +77 -75
  124. package/dist/src/sessionStore.js +3 -3
  125. package/dist/src/version.js +10 -10
  126. package/dist/vendor/oracle-notifier/README.md +2 -0
  127. package/package.json +66 -62
  128. package/vendor/oracle-notifier/README.md +2 -0
  129. package/dist/markdansi/types/index.js +0 -4
  130. package/dist/oracle/bin/oracle-cli.js +0 -472
  131. package/dist/oracle/src/browser/actions/assistantResponse.js +0 -471
  132. package/dist/oracle/src/browser/actions/attachments.js +0 -82
  133. package/dist/oracle/src/browser/actions/modelSelection.js +0 -190
  134. package/dist/oracle/src/browser/actions/navigation.js +0 -75
  135. package/dist/oracle/src/browser/actions/promptComposer.js +0 -167
  136. package/dist/oracle/src/browser/chromeLifecycle.js +0 -104
  137. package/dist/oracle/src/browser/config.js +0 -33
  138. package/dist/oracle/src/browser/constants.js +0 -40
  139. package/dist/oracle/src/browser/cookies.js +0 -210
  140. package/dist/oracle/src/browser/domDebug.js +0 -36
  141. package/dist/oracle/src/browser/index.js +0 -331
  142. package/dist/oracle/src/browser/pageActions.js +0 -5
  143. package/dist/oracle/src/browser/prompt.js +0 -88
  144. package/dist/oracle/src/browser/promptSummary.js +0 -20
  145. package/dist/oracle/src/browser/sessionRunner.js +0 -80
  146. package/dist/oracle/src/browser/types.js +0 -1
  147. package/dist/oracle/src/browser/utils.js +0 -62
  148. package/dist/oracle/src/browserMode.js +0 -1
  149. package/dist/oracle/src/cli/browserConfig.js +0 -44
  150. package/dist/oracle/src/cli/dryRun.js +0 -59
  151. package/dist/oracle/src/cli/engine.js +0 -17
  152. package/dist/oracle/src/cli/errorUtils.js +0 -9
  153. package/dist/oracle/src/cli/help.js +0 -70
  154. package/dist/oracle/src/cli/markdownRenderer.js +0 -15
  155. package/dist/oracle/src/cli/options.js +0 -103
  156. package/dist/oracle/src/cli/promptRequirement.js +0 -14
  157. package/dist/oracle/src/cli/rootAlias.js +0 -30
  158. package/dist/oracle/src/cli/sessionCommand.js +0 -77
  159. package/dist/oracle/src/cli/sessionDisplay.js +0 -270
  160. package/dist/oracle/src/cli/sessionRunner.js +0 -94
  161. package/dist/oracle/src/heartbeat.js +0 -43
  162. package/dist/oracle/src/oracle/client.js +0 -48
  163. package/dist/oracle/src/oracle/config.js +0 -29
  164. package/dist/oracle/src/oracle/errors.js +0 -101
  165. package/dist/oracle/src/oracle/files.js +0 -220
  166. package/dist/oracle/src/oracle/format.js +0 -33
  167. package/dist/oracle/src/oracle/fsAdapter.js +0 -7
  168. package/dist/oracle/src/oracle/oscProgress.js +0 -60
  169. package/dist/oracle/src/oracle/request.js +0 -48
  170. package/dist/oracle/src/oracle/run.js +0 -444
  171. package/dist/oracle/src/oracle/tokenStats.js +0 -39
  172. package/dist/oracle/src/oracle/types.js +0 -1
  173. package/dist/oracle/src/oracle.js +0 -9
  174. package/dist/oracle/src/sessionManager.js +0 -205
  175. package/dist/oracle/src/version.js +0 -39
  176. package/dist/scripts/chrome/browser-tools.js +0 -295
  177. package/dist/src/browser/profileSync.js +0 -141
@@ -1,13 +1,13 @@
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';
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";
11
11
  const isTty = () => Boolean(process.stdout.isTTY);
12
12
  const dim = (text) => (isTty() ? kleur.dim(text) : text);
13
13
  export const MAX_RENDER_BYTES = 200_000;
@@ -20,10 +20,10 @@ function isProcessAlive(pid) {
20
20
  }
21
21
  catch (error) {
22
22
  const code = error instanceof Error ? error.code : undefined;
23
- if (code === 'ESRCH' || code === 'EINVAL') {
23
+ if (code === "ESRCH" || code === "EINVAL") {
24
24
  return false;
25
25
  }
26
- if (code === 'EPERM') {
26
+ if (code === "EPERM") {
27
27
  return true;
28
28
  }
29
29
  return true;
@@ -32,8 +32,14 @@ function isProcessAlive(pid) {
32
32
  const CLEANUP_TIP = 'Tip: Run "oracle session --clear --hours 24" to prune cached runs (add --all to wipe everything).';
33
33
  export async function showStatus({ hours, includeAll, limit, showExamples = false, modelFilter, }) {
34
34
  const metas = await sessionStore.listSessions();
35
- const { entries, truncated, total } = sessionStore.filterSessions(metas, { hours, includeAll, limit });
36
- const filteredEntries = modelFilter ? entries.filter((entry) => matchesModel(entry, modelFilter)) : entries;
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;
37
43
  const richTty = process.stdout.isTTY && chalk.level > 0;
38
44
  const responseOwners = buildResponseOwnerIndex(metas);
39
45
  if (!filteredEntries.length) {
@@ -43,7 +49,7 @@ export async function showStatus({ hours, includeAll, limit, showExamples = fals
43
49
  }
44
50
  return;
45
51
  }
46
- console.log(chalk.bold('Recent Sessions'));
52
+ console.log(chalk.bold("Recent Sessions"));
47
53
  console.log(formatSessionTableHeader(richTty));
48
54
  const treeRows = buildStatusTreeRows(filteredEntries, responseOwners);
49
55
  for (const row of treeRows) {
@@ -52,7 +58,7 @@ export async function showStatus({ hours, includeAll, limit, showExamples = fals
52
58
  ? richTty
53
59
  ? chalk.gray(` <- ${row.detachedParentLabel}`)
54
60
  : ` <- ${row.detachedParentLabel}`
55
- : '';
61
+ : "";
56
62
  console.log(`${line}${detachedParent}`);
57
63
  }
58
64
  if (truncated) {
@@ -70,7 +76,7 @@ export async function attachSession(sessionId, options) {
70
76
  process.exitCode = 1;
71
77
  return;
72
78
  }
73
- if (metadata.mode === 'browser' && metadata.status === 'running' && !metadata.browser?.runtime) {
79
+ if (metadata.mode === "browser" && metadata.status === "running" && !metadata.browser?.runtime) {
74
80
  await wait(250);
75
81
  const refreshed = await sessionStore.readSession(sessionId);
76
82
  if (refreshed) {
@@ -92,16 +98,16 @@ export async function attachSession(sessionId, options) {
92
98
  const isVerbose = Boolean(process.env.ORACLE_VERBOSE_RENDER);
93
99
  const runtime = metadata.browser?.runtime;
94
100
  const controllerAlive = isProcessAlive(runtime?.controllerPid);
95
- const hasChromeDisconnect = metadata.response?.incompleteReason === 'chrome-disconnected';
96
- const statusAllowsReattach = metadata.status === 'running' || (metadata.status === 'error' && hasChromeDisconnect);
101
+ const hasChromeDisconnect = metadata.response?.incompleteReason === "chrome-disconnected";
102
+ const statusAllowsReattach = metadata.status === "running" || (metadata.status === "error" && hasChromeDisconnect);
97
103
  const hasFallbackSessionInfo = Boolean(runtime?.chromePort || runtime?.tabUrl || runtime?.conversationId);
98
104
  const canReattach = statusAllowsReattach &&
99
- metadata.mode === 'browser' &&
105
+ metadata.mode === "browser" &&
100
106
  hasFallbackSessionInfo &&
101
107
  (hasChromeDisconnect || (runtime?.controllerPid && !controllerAlive));
102
108
  if (canReattach) {
103
- const portInfo = runtime?.chromePort ? `port ${runtime.chromePort}` : 'unknown port';
104
- const urlInfo = runtime?.tabUrl ? `url=${runtime.tabUrl}` : 'url=unknown';
109
+ const portInfo = runtime?.chromePort ? `port ${runtime.chromePort}` : "unknown port";
110
+ const urlInfo = runtime?.tabUrl ? `url=${runtime.tabUrl}` : "url=unknown";
105
111
  console.log(chalk.yellow(`Attempting to reattach to the existing Chrome session (${portInfo}, ${urlInfo})...`));
106
112
  try {
107
113
  const result = await resumeBrowserSession(runtime, metadata.browser?.config, Object.assign(((message) => {
@@ -111,13 +117,13 @@ export async function attachSession(sessionId, options) {
111
117
  }), { verbose: true }), { promptPreview: metadata.promptPreview });
112
118
  const outputTokens = estimateTokenCount(result.answerMarkdown);
113
119
  const logWriter = sessionStore.createLogWriter(sessionId);
114
- logWriter.logLine('[reattach] captured assistant response from existing Chrome tab');
115
- logWriter.logLine('Answer:');
120
+ logWriter.logLine("[reattach] captured assistant response from existing Chrome tab");
121
+ logWriter.logLine("Answer:");
116
122
  logWriter.logLine(result.answerMarkdown || result.answerText);
117
123
  logWriter.stream.end();
118
124
  if (metadata.model) {
119
125
  await sessionStore.updateModelRun(metadata.id, metadata.model, {
120
- status: 'completed',
126
+ status: "completed",
121
127
  usage: {
122
128
  inputTokens: 0,
123
129
  outputTokens,
@@ -128,7 +134,7 @@ export async function attachSession(sessionId, options) {
128
134
  });
129
135
  }
130
136
  await sessionStore.updateSession(sessionId, {
131
- status: 'completed',
137
+ status: "completed",
132
138
  completedAt: new Date().toISOString(),
133
139
  usage: {
134
140
  inputTokens: 0,
@@ -140,11 +146,11 @@ export async function attachSession(sessionId, options) {
140
146
  config: metadata.browser?.config,
141
147
  runtime,
142
148
  },
143
- response: { status: 'completed' },
149
+ response: { status: "completed" },
144
150
  error: undefined,
145
151
  transport: undefined,
146
152
  });
147
- console.log(chalk.green('Reattach succeeded; session marked completed.'));
153
+ console.log(chalk.green("Reattach succeeded; session marked completed."));
148
154
  metadata = (await sessionStore.readSession(sessionId)) ?? metadata;
149
155
  }
150
156
  catch (error) {
@@ -164,11 +170,11 @@ export async function attachSession(sessionId, options) {
164
170
  console.log(`Created: ${metadata.createdAt}`);
165
171
  console.log(`Status: ${metadata.status}`);
166
172
  if (metadata.models && metadata.models.length > 0) {
167
- console.log('Models:');
173
+ console.log("Models:");
168
174
  for (const run of metadata.models) {
169
175
  const usage = run.usage
170
176
  ? ` tok=${formatTokenCount(run.usage.outputTokens ?? 0)}/${formatTokenCount(run.usage.totalTokens ?? 0)}`
171
- : '';
177
+ : "";
172
178
  console.log(`- ${chalk.cyan(run.model)} — ${run.status}${usage}`);
173
179
  }
174
180
  }
@@ -188,19 +194,19 @@ export async function attachSession(sessionId, options) {
188
194
  console.log(dim(`User error: ${userErrorSummary}`));
189
195
  }
190
196
  }
191
- const shouldTrimIntro = initialStatus === 'completed' || initialStatus === 'error';
197
+ const shouldTrimIntro = initialStatus === "completed" || initialStatus === "error";
192
198
  if (options?.renderPrompt !== false) {
193
199
  const prompt = await readStoredPrompt(sessionId);
194
200
  if (prompt) {
195
- console.log(chalk.bold('Prompt:'));
201
+ console.log(chalk.bold("Prompt:"));
196
202
  console.log(renderMarkdownAnsi(prompt));
197
- console.log(dim('---'));
203
+ console.log(dim("---"));
198
204
  }
199
205
  }
200
206
  if (shouldTrimIntro) {
201
207
  const fullLog = await buildSessionLogForDisplay(sessionId, metadata, normalizedModelFilter);
202
208
  const trimmed = trimBeforeFirstAnswer(fullLog);
203
- const size = Buffer.byteLength(trimmed, 'utf8');
209
+ const size = Buffer.byteLength(trimmed, "utf8");
204
210
  const canRender = wantsRender && isTty() && size <= MAX_RENDER_BYTES;
205
211
  if (wantsRender && size > MAX_RENDER_BYTES) {
206
212
  const msg = `Render skipped (log too large: ${size} bytes > ${MAX_RENDER_BYTES}). Showing raw text.`;
@@ -210,7 +216,7 @@ export async function attachSession(sessionId, options) {
210
216
  }
211
217
  }
212
218
  else if (wantsRender && !isTty()) {
213
- const msg = 'Render requested but stdout is not a TTY; showing raw text.';
219
+ const msg = "Render requested but stdout is not a TTY; showing raw text.";
214
220
  console.log(dim(msg));
215
221
  if (isVerbose) {
216
222
  console.log(dim(`Verbose: renderMarkdown=true tty=${isTty()} size=${size}`));
@@ -232,13 +238,20 @@ export async function attachSession(sessionId, options) {
232
238
  return;
233
239
  }
234
240
  if (wantsRender) {
235
- console.log(dim('Render will apply after completion; streaming raw text meanwhile...'));
241
+ console.log(dim("Render will apply after completion; streaming raw text meanwhile..."));
236
242
  if (isVerbose) {
237
243
  console.log(dim(`Verbose: streaming phase renderMarkdown=true tty=${isTty()}`));
238
244
  }
239
245
  }
240
246
  const liveRenderState = wantsRender && isTty()
241
- ? { pending: '', inFence: false, inTable: false, renderedBytes: 0, fallback: false, noticedFallback: false }
247
+ ? {
248
+ pending: "",
249
+ inFence: false,
250
+ inTable: false,
251
+ renderedBytes: 0,
252
+ fallback: false,
253
+ noticedFallback: false,
254
+ }
242
255
  : null;
243
256
  let lastLength = 0;
244
257
  const renderLiveChunk = (chunk) => {
@@ -254,7 +267,7 @@ export async function attachSession(sessionId, options) {
254
267
  const { chunks, remainder } = extractRenderableChunks(liveRenderState.pending, liveRenderState);
255
268
  liveRenderState.pending = remainder;
256
269
  for (const candidate of chunks) {
257
- const projected = liveRenderState.renderedBytes + Buffer.byteLength(candidate, 'utf8');
270
+ const projected = liveRenderState.renderedBytes + Buffer.byteLength(candidate, "utf8");
258
271
  if (projected > MAX_RENDER_BYTES) {
259
272
  if (!liveRenderState.noticedFallback) {
260
273
  console.log(dim(`Render skipped (log too large: > ${MAX_RENDER_BYTES} bytes). Showing raw text.`));
@@ -262,11 +275,11 @@ export async function attachSession(sessionId, options) {
262
275
  }
263
276
  liveRenderState.fallback = true;
264
277
  process.stdout.write(candidate + liveRenderState.pending);
265
- liveRenderState.pending = '';
278
+ liveRenderState.pending = "";
266
279
  return;
267
280
  }
268
281
  process.stdout.write(renderMarkdownAnsi(candidate));
269
- liveRenderState.renderedBytes += Buffer.byteLength(candidate, 'utf8');
282
+ liveRenderState.renderedBytes += Buffer.byteLength(candidate, "utf8");
270
283
  }
271
284
  };
272
285
  const flushRemainder = () => {
@@ -277,8 +290,8 @@ export async function attachSession(sessionId, options) {
277
290
  return;
278
291
  }
279
292
  const text = liveRenderState.pending;
280
- liveRenderState.pending = '';
281
- const projected = liveRenderState.renderedBytes + Buffer.byteLength(text, 'utf8');
293
+ liveRenderState.pending = "";
294
+ const projected = liveRenderState.renderedBytes + Buffer.byteLength(text, "utf8");
282
295
  if (projected > MAX_RENDER_BYTES) {
283
296
  if (!liveRenderState.noticedFallback) {
284
297
  console.log(dim(`Render skipped (log too large: > ${MAX_RENDER_BYTES} bytes). Showing raw text.`));
@@ -304,15 +317,15 @@ export async function attachSession(sessionId, options) {
304
317
  if (!latest) {
305
318
  break;
306
319
  }
307
- if (latest.status === 'completed' || latest.status === 'error') {
320
+ if (latest.status === "completed" || latest.status === "error") {
308
321
  await printNew();
309
322
  flushRemainder();
310
323
  if (!options?.suppressMetadata) {
311
- if (latest.status === 'error' && latest.errorMessage) {
312
- console.log('\nResult:');
324
+ if (latest.status === "error" && latest.errorMessage) {
325
+ console.log("\nResult:");
313
326
  console.log(`Session failed: ${latest.errorMessage}`);
314
327
  }
315
- if (latest.status === 'completed' && latest.usage) {
328
+ if (latest.status === "completed" && latest.usage) {
316
329
  const summary = formatCompletionSummary(latest, { includeSlug: true });
317
330
  if (summary) {
318
331
  console.log(`\n${chalk.green.bold(summary)}`);
@@ -346,19 +359,19 @@ export function formatResponseMetadata(metadata) {
346
359
  if (metadata.incompleteReason) {
347
360
  parts.push(`incomplete=${metadata.incompleteReason}`);
348
361
  }
349
- return parts.length > 0 ? parts.join(' | ') : null;
362
+ return parts.length > 0 ? parts.join(" | ") : null;
350
363
  }
351
364
  export function formatTransportMetadata(metadata) {
352
365
  if (!metadata?.reason) {
353
366
  return null;
354
367
  }
355
368
  const reasonLabels = {
356
- 'client-timeout': 'client timeout (deadline exceeded)',
357
- 'connection-lost': 'connection lost before completion',
358
- 'client-abort': 'request aborted locally',
359
- unknown: 'unknown transport failure',
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",
360
373
  };
361
- const label = reasonLabels[metadata.reason] ?? 'transport error';
374
+ const label = reasonLabels[metadata.reason] ?? "transport error";
362
375
  return `${metadata.reason} — ${label}`;
363
376
  }
364
377
  export function formatUserErrorMetadata(metadata) {
@@ -375,7 +388,7 @@ export function formatUserErrorMetadata(metadata) {
375
388
  if (metadata.details && Object.keys(metadata.details).length > 0) {
376
389
  parts.push(`details=${JSON.stringify(metadata.details)}`);
377
390
  }
378
- return parts.length > 0 ? parts.join(' | ') : null;
391
+ return parts.length > 0 ? parts.join(" | ") : null;
379
392
  }
380
393
  export function buildReattachLine(metadata) {
381
394
  if (!metadata.id) {
@@ -389,13 +402,13 @@ export function buildReattachLine(metadata) {
389
402
  if (!elapsedLabel) {
390
403
  return null;
391
404
  }
392
- if (metadata.status === 'running') {
405
+ if (metadata.status === "running") {
393
406
  return `Session ${metadata.id} reattached, request started ${elapsedLabel} ago.`;
394
407
  }
395
408
  return null;
396
409
  }
397
410
  export function trimBeforeFirstAnswer(logText) {
398
- const marker = 'Answer:';
411
+ const marker = "Answer:";
399
412
  const index = logText.indexOf(marker);
400
413
  if (index === -1) {
401
414
  return logText;
@@ -427,7 +440,7 @@ function formatRelativeDuration(referenceIso) {
427
440
  if (remainingMinutes > 0) {
428
441
  parts.push(`${remainingMinutes}m`);
429
442
  }
430
- return parts.join(' ');
443
+ return parts.join(" ");
431
444
  }
432
445
  const days = Math.floor(hours / 24);
433
446
  const remainingHours = hours % 24;
@@ -438,17 +451,17 @@ function formatRelativeDuration(referenceIso) {
438
451
  if (remainingMinutes > 0 && days === 0) {
439
452
  parts.push(`${remainingMinutes}m`);
440
453
  }
441
- return parts.join(' ');
454
+ return parts.join(" ");
442
455
  }
443
456
  function printStatusExamples() {
444
- console.log('');
445
- console.log(chalk.bold('Usage Examples'));
446
- console.log(`${chalk.bold(' oracle status --hours 72 --limit 50')}`);
447
- console.log(dim(' Show 72h of history capped at 50 entries.'));
448
- console.log(`${chalk.bold(' oracle status --clear --hours 168')}`);
449
- console.log(dim(' Delete sessions older than 7 days (use --all to wipe everything).'));
450
- console.log(`${chalk.bold(' oracle session <session-id>')}`);
451
- console.log(dim(' Attach to a specific running/completed session to stream its output.'));
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."));
452
465
  console.log(dim(CLEANUP_TIP));
453
466
  }
454
467
  function matchesModel(entry, filter) {
@@ -456,7 +469,8 @@ function matchesModel(entry, filter) {
456
469
  if (!normalized) {
457
470
  return true;
458
471
  }
459
- const models = entry.models?.map((model) => model.model.toLowerCase()) ?? (entry.model ? [entry.model.toLowerCase()] : []);
472
+ const models = entry.models?.map((model) => model.model.toLowerCase()) ??
473
+ (entry.model ? [entry.model.toLowerCase()] : []);
460
474
  return models.includes(normalized);
461
475
  }
462
476
  function buildStatusTreeRows(entries, responseOwners) {
@@ -485,8 +499,8 @@ function buildStatusTreeRows(entries, responseOwners) {
485
499
  }
486
500
  visited.add(entry.id);
487
501
  const children = childMap.get(entry.id) ?? [];
488
- const nodeBranch = isLast ? '└─ ' : '├─ ';
489
- const prefix = `${ancestorHasMore.map((hasMore) => (hasMore ? '' : ' ')).join('')}${nodeBranch}`;
502
+ const nodeBranch = isLast ? "└─ " : "├─ ";
503
+ const prefix = `${ancestorHasMore.map((hasMore) => (hasMore ? "" : " ")).join("")}${nodeBranch}`;
490
504
  rows.push({ entry, displaySlug: `${prefix}${entry.id}` });
491
505
  children.forEach((child, index) => {
492
506
  walkChild(child, [...ancestorHasMore, !isLast], index === children.length - 1);
@@ -549,12 +563,12 @@ async function buildSessionLogForDisplay(sessionId, fallbackMeta, modelFilter) {
549
563
  ? models.filter((model) => model.model.toLowerCase() === normalizedFilter)
550
564
  : models;
551
565
  if (candidates.length === 0) {
552
- return '';
566
+ return "";
553
567
  }
554
568
  const sections = [];
555
569
  let hasContent = false;
556
570
  for (const model of candidates) {
557
- const body = (await sessionStore.readModelLog(sessionId, model.model)) ?? '';
571
+ const body = (await sessionStore.readModelLog(sessionId, model.model)) ?? "";
558
572
  if (body.trim().length > 0) {
559
573
  hasContent = true;
560
574
  }
@@ -564,18 +578,18 @@ async function buildSessionLogForDisplay(sessionId, fallbackMeta, modelFilter) {
564
578
  // Fallback for runs that recorded output only in the session log (e.g., browser runs without per-model logs).
565
579
  return await sessionStore.readLog(sessionId);
566
580
  }
567
- return sections.join('\n\n');
581
+ return sections.join("\n\n");
568
582
  }
569
583
  function extractRenderableChunks(text, state) {
570
584
  const chunks = [];
571
- let buffer = '';
585
+ let buffer = "";
572
586
  const lines = text.split(/(\n)/);
573
587
  for (let i = 0; i < lines.length; i += 1) {
574
588
  const segment = lines[i];
575
- if (segment === '\n') {
589
+ if (segment === "\n") {
576
590
  buffer += segment;
577
591
  // Detect code fences
578
- const prev = lines[i - 1] ?? '';
592
+ const prev = lines[i - 1] ?? "";
579
593
  const fenceMatch = prev.match(/^(\s*)(`{3,}|~{3,})(.*)$/);
580
594
  if (!state.inFence && fenceMatch) {
581
595
  state.inFence = true;
@@ -587,17 +601,17 @@ function extractRenderableChunks(text, state) {
587
601
  }
588
602
  const trimmed = prev.trim();
589
603
  if (!state.inFence) {
590
- if (!state.inTable && trimmed.startsWith('|') && trimmed.includes('|')) {
604
+ if (!state.inTable && trimmed.startsWith("|") && trimmed.includes("|")) {
591
605
  state.inTable = true;
592
606
  }
593
- if (state.inTable && trimmed === '') {
607
+ if (state.inTable && trimmed === "") {
594
608
  state.inTable = false;
595
609
  }
596
610
  }
597
- const safeBreak = !state.inFence && !state.inTable && trimmed === '';
611
+ const safeBreak = !state.inFence && !state.inTable && trimmed === "";
598
612
  if (safeBreak) {
599
613
  chunks.push(buffer);
600
- buffer = '';
614
+ buffer = "";
601
615
  }
602
616
  continue;
603
617
  }
@@ -609,7 +623,7 @@ export function formatCompletionSummary(metadata, options = {}) {
609
623
  if (!metadata.usage || metadata.elapsedMs == null) {
610
624
  return null;
611
625
  }
612
- const modeLabel = metadata.mode === 'browser' ? `${metadata.model ?? 'n/a'}[browser]` : metadata.model ?? 'n/a';
626
+ const modeLabel = metadata.mode === "browser" ? `${metadata.model ?? "n/a"}[browser]` : (metadata.model ?? "n/a");
613
627
  const usage = metadata.usage;
614
628
  const cost = resolveSessionCost(metadata);
615
629
  const tokensDisplay = [
@@ -624,9 +638,9 @@ export function formatCompletionSummary(metadata, options = {}) {
624
638
  reasoning_tokens: usage.reasoningTokens,
625
639
  total_tokens: usage.totalTokens,
626
640
  }, index))
627
- .join('/');
641
+ .join("/");
628
642
  const tokensPart = (() => {
629
- const parts = tokensDisplay.split('/');
643
+ const parts = tokensDisplay.split("/");
630
644
  if (parts.length !== 4)
631
645
  return tokensDisplay;
632
646
  return `↑${parts[0]} ↓${parts[1]} ↻${parts[2]} Δ${parts[3]}`;
@@ -1,8 +1,12 @@
1
1
  function readResponseId(record) {
2
2
  if (!record)
3
3
  return null;
4
- const candidate = typeof record.responseId === 'string' ? record.responseId : typeof record.id === 'string' ? record.id : null;
5
- if (!candidate || !candidate.startsWith('resp_')) {
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_")) {
6
10
  return null;
7
11
  }
8
12
  return candidate;