@steipete/oracle 1.0.8 → 1.2.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 (106) hide show
  1. package/README.md +32 -4
  2. package/assets-oracle-icon.png +0 -0
  3. package/dist/bin/oracle-cli.js +178 -21
  4. package/dist/bin/oracle-mcp.js +6 -0
  5. package/dist/markdansi/types/index.js +4 -0
  6. package/dist/oracle/bin/oracle-cli.js +472 -0
  7. package/dist/oracle/src/browser/actions/assistantResponse.js +471 -0
  8. package/dist/oracle/src/browser/actions/attachments.js +82 -0
  9. package/dist/oracle/src/browser/actions/modelSelection.js +190 -0
  10. package/dist/oracle/src/browser/actions/navigation.js +75 -0
  11. package/dist/oracle/src/browser/actions/promptComposer.js +167 -0
  12. package/dist/oracle/src/browser/chromeLifecycle.js +104 -0
  13. package/dist/oracle/src/browser/config.js +33 -0
  14. package/dist/oracle/src/browser/constants.js +40 -0
  15. package/dist/oracle/src/browser/cookies.js +210 -0
  16. package/dist/oracle/src/browser/domDebug.js +36 -0
  17. package/dist/oracle/src/browser/index.js +331 -0
  18. package/dist/oracle/src/browser/pageActions.js +5 -0
  19. package/dist/oracle/src/browser/prompt.js +88 -0
  20. package/dist/oracle/src/browser/promptSummary.js +20 -0
  21. package/dist/oracle/src/browser/sessionRunner.js +80 -0
  22. package/dist/oracle/src/browser/types.js +1 -0
  23. package/dist/oracle/src/browser/utils.js +62 -0
  24. package/dist/oracle/src/browserMode.js +1 -0
  25. package/dist/oracle/src/cli/browserConfig.js +44 -0
  26. package/dist/oracle/src/cli/dryRun.js +59 -0
  27. package/dist/oracle/src/cli/engine.js +17 -0
  28. package/dist/oracle/src/cli/errorUtils.js +9 -0
  29. package/dist/oracle/src/cli/help.js +70 -0
  30. package/dist/oracle/src/cli/markdownRenderer.js +15 -0
  31. package/dist/oracle/src/cli/options.js +103 -0
  32. package/dist/oracle/src/cli/promptRequirement.js +14 -0
  33. package/dist/oracle/src/cli/rootAlias.js +30 -0
  34. package/dist/oracle/src/cli/sessionCommand.js +77 -0
  35. package/dist/oracle/src/cli/sessionDisplay.js +270 -0
  36. package/dist/oracle/src/cli/sessionRunner.js +94 -0
  37. package/dist/oracle/src/heartbeat.js +43 -0
  38. package/dist/oracle/src/oracle/client.js +48 -0
  39. package/dist/oracle/src/oracle/config.js +29 -0
  40. package/dist/oracle/src/oracle/errors.js +101 -0
  41. package/dist/oracle/src/oracle/files.js +220 -0
  42. package/dist/oracle/src/oracle/format.js +33 -0
  43. package/dist/oracle/src/oracle/fsAdapter.js +7 -0
  44. package/dist/oracle/src/oracle/oscProgress.js +60 -0
  45. package/dist/oracle/src/oracle/request.js +48 -0
  46. package/dist/oracle/src/oracle/run.js +444 -0
  47. package/dist/oracle/src/oracle/tokenStats.js +39 -0
  48. package/dist/oracle/src/oracle/types.js +1 -0
  49. package/dist/oracle/src/oracle.js +9 -0
  50. package/dist/oracle/src/sessionManager.js +205 -0
  51. package/dist/oracle/src/version.js +39 -0
  52. package/dist/src/browser/actions/modelSelection.js +117 -29
  53. package/dist/src/browser/cookies.js +1 -1
  54. package/dist/src/browser/index.js +2 -1
  55. package/dist/src/browser/prompt.js +6 -5
  56. package/dist/src/browser/sessionRunner.js +4 -2
  57. package/dist/src/cli/dryRun.js +41 -5
  58. package/dist/src/cli/engine.js +7 -0
  59. package/dist/src/cli/help.js +1 -1
  60. package/dist/src/cli/hiddenAliases.js +17 -0
  61. package/dist/src/cli/markdownRenderer.js +97 -0
  62. package/dist/src/cli/notifier.js +223 -0
  63. package/dist/src/cli/promptRequirement.js +3 -0
  64. package/dist/src/cli/rootAlias.js +14 -0
  65. package/dist/src/cli/runOptions.js +29 -0
  66. package/dist/src/cli/sessionCommand.js +60 -2
  67. package/dist/src/cli/sessionDisplay.js +222 -10
  68. package/dist/src/cli/sessionRunner.js +21 -2
  69. package/dist/src/cli/tui/index.js +436 -0
  70. package/dist/src/config.js +27 -0
  71. package/dist/src/mcp/server.js +36 -0
  72. package/dist/src/mcp/tools/consult.js +158 -0
  73. package/dist/src/mcp/tools/sessionResources.js +64 -0
  74. package/dist/src/mcp/tools/sessions.js +106 -0
  75. package/dist/src/mcp/types.js +17 -0
  76. package/dist/src/mcp/utils.js +24 -0
  77. package/dist/src/oracle/files.js +143 -6
  78. package/dist/src/oracle/oscProgress.js +60 -0
  79. package/dist/src/oracle/run.js +104 -71
  80. package/dist/src/oracle/tokenEstimate.js +34 -0
  81. package/dist/src/sessionManager.js +65 -3
  82. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  83. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +20 -0
  84. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  85. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
  86. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +128 -0
  87. package/dist/vendor/oracle-notifier/OracleNotifier.swift +45 -0
  88. package/dist/vendor/oracle-notifier/README.md +24 -0
  89. package/dist/vendor/oracle-notifier/build-notifier.sh +93 -0
  90. package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  91. package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/Info.plist +20 -0
  92. package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  93. package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
  94. package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +128 -0
  95. package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.swift +45 -0
  96. package/dist/vendor/oracle-notifier/oracle-notifier/README.md +24 -0
  97. package/dist/vendor/oracle-notifier/oracle-notifier/build-notifier.sh +93 -0
  98. package/package.json +27 -9
  99. package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  100. package/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +20 -0
  101. package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  102. package/vendor/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
  103. package/vendor/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +128 -0
  104. package/vendor/oracle-notifier/OracleNotifier.swift +45 -0
  105. package/vendor/oracle-notifier/README.md +24 -0
  106. package/vendor/oracle-notifier/build-notifier.sh +93 -0
@@ -1,8 +1,12 @@
1
1
  import chalk from 'chalk';
2
2
  import kleur from 'kleur';
3
- import { filterSessionsByRange, listSessionsMetadata, readSessionLog, readSessionMetadata, SESSIONS_DIR, wait, } from '../sessionManager.js';
4
- const isTty = process.stdout.isTTY;
5
- const dim = (text) => (isTty ? kleur.dim(text) : text);
3
+ import { filterSessionsByRange, listSessionsMetadata, readSessionLog, readSessionMetadata, readSessionRequest, SESSIONS_DIR, wait, } from '../sessionManager.js';
4
+ import { renderMarkdownAnsi } from './markdownRenderer.js';
5
+ import { formatElapsed, formatUSD } from '../oracle/format.js';
6
+ import { MODEL_CONFIGS } from '../oracle.js';
7
+ const isTty = () => Boolean(process.stdout.isTTY);
8
+ const dim = (text) => (isTty() ? kleur.dim(text) : text);
9
+ export const MAX_RENDER_BYTES = 200_000;
6
10
  const CLEANUP_TIP = 'Tip: Run "oracle session --clear --hours 24" to prune cached runs (add --all to wipe everything).';
7
11
  export async function showStatus({ hours, includeAll, limit, showExamples = false }) {
8
12
  const metas = await listSessionsMetadata();
@@ -16,12 +20,17 @@ export async function showStatus({ hours, includeAll, limit, showExamples = fals
16
20
  return;
17
21
  }
18
22
  console.log(chalk.bold('Recent Sessions'));
23
+ console.log(chalk.dim('Timestamp Chars Cost Status Model ID'));
19
24
  for (const entry of entries) {
20
25
  const statusRaw = (entry.status || 'unknown').padEnd(9);
21
26
  const status = richTty ? colorStatus(entry.status ?? 'unknown', statusRaw) : statusRaw;
22
27
  const model = (entry.model || 'n/a').padEnd(9);
23
- const created = entry.createdAt.replace('T', ' ').replace('Z', '');
24
- console.log(`${created} | ${status} | ${model} | ${entry.id}`);
28
+ const created = formatTimestamp(entry.createdAt);
29
+ const chars = entry.options?.prompt?.length ?? entry.promptPreview?.length ?? 0;
30
+ const charLabel = chars > 0 ? String(chars).padStart(5) : ' -';
31
+ const costValue = resolveCost(entry);
32
+ const costLabel = costValue != null ? formatCostTable(costValue) : ' -';
33
+ console.log(`${created} | ${charLabel} | ${costLabel} | ${status} | ${model} | ${entry.id}`);
25
34
  }
26
35
  if (truncated) {
27
36
  console.log(chalk.yellow(`Showing ${entries.length} of ${total} sessions from the requested range. Run "oracle session --clear" or delete entries in ${SESSIONS_DIR} to free space, or rerun with --status-limit/--status-all.`));
@@ -50,6 +59,8 @@ export async function attachSession(sessionId, options) {
50
59
  return;
51
60
  }
52
61
  const initialStatus = metadata.status;
62
+ const wantsRender = Boolean(options?.renderMarkdown);
63
+ const isVerbose = Boolean(process.env.ORACLE_VERBOSE_RENDER);
53
64
  if (!options?.suppressMetadata) {
54
65
  const reattachLine = buildReattachLine(metadata);
55
66
  if (reattachLine) {
@@ -72,18 +83,111 @@ export async function attachSession(sessionId, options) {
72
83
  }
73
84
  }
74
85
  const shouldTrimIntro = initialStatus === 'completed' || initialStatus === 'error';
86
+ if (options?.renderPrompt !== false) {
87
+ const prompt = await readStoredPrompt(sessionId);
88
+ if (prompt) {
89
+ console.log(chalk.bold('Prompt:'));
90
+ console.log(renderMarkdownAnsi(prompt));
91
+ console.log(dim('---'));
92
+ }
93
+ }
75
94
  if (shouldTrimIntro) {
76
95
  const fullLog = await readSessionLog(sessionId);
77
96
  const trimmed = trimBeforeFirstAnswer(fullLog);
78
- process.stdout.write(trimmed);
97
+ const size = Buffer.byteLength(trimmed, 'utf8');
98
+ const canRender = wantsRender && isTty() && size <= MAX_RENDER_BYTES;
99
+ if (wantsRender && size > MAX_RENDER_BYTES) {
100
+ const msg = `Render skipped (log too large: ${size} bytes > ${MAX_RENDER_BYTES}). Showing raw text.`;
101
+ console.log(dim(msg));
102
+ if (isVerbose) {
103
+ console.log(dim(`Verbose: renderMarkdown=true tty=${isTty()} size=${size}`));
104
+ }
105
+ }
106
+ else if (wantsRender && !isTty()) {
107
+ const msg = 'Render requested but stdout is not a TTY; showing raw text.';
108
+ console.log(dim(msg));
109
+ if (isVerbose) {
110
+ console.log(dim(`Verbose: renderMarkdown=true tty=${isTty()} size=${size}`));
111
+ }
112
+ }
113
+ if (canRender) {
114
+ if (isVerbose) {
115
+ console.log(dim(`Verbose: rendering markdown (size=${size}, tty=${isTty()})`));
116
+ }
117
+ process.stdout.write(renderMarkdownAnsi(trimmed));
118
+ }
119
+ else {
120
+ process.stdout.write(trimmed);
121
+ }
122
+ const summary = formatCompletionSummary(metadata, { includeSlug: true });
123
+ if (summary) {
124
+ console.log(`\n${chalk.green.bold(summary)}`);
125
+ }
79
126
  return;
80
127
  }
128
+ if (wantsRender) {
129
+ console.log(dim('Render will apply after completion; streaming raw text meanwhile...'));
130
+ if (isVerbose) {
131
+ console.log(dim(`Verbose: streaming phase renderMarkdown=true tty=${isTty()}`));
132
+ }
133
+ }
134
+ const liveRenderState = wantsRender && isTty()
135
+ ? { pending: '', inFence: false, inTable: false, renderedBytes: 0, fallback: false, noticedFallback: false }
136
+ : null;
81
137
  let lastLength = 0;
138
+ const renderLiveChunk = (chunk) => {
139
+ if (!liveRenderState || chunk.length === 0) {
140
+ process.stdout.write(chunk);
141
+ return;
142
+ }
143
+ if (liveRenderState.fallback) {
144
+ process.stdout.write(chunk);
145
+ return;
146
+ }
147
+ liveRenderState.pending += chunk;
148
+ const { chunks, remainder } = extractRenderableChunks(liveRenderState.pending, liveRenderState);
149
+ liveRenderState.pending = remainder;
150
+ for (const candidate of chunks) {
151
+ const projected = liveRenderState.renderedBytes + Buffer.byteLength(candidate, 'utf8');
152
+ if (projected > MAX_RENDER_BYTES) {
153
+ if (!liveRenderState.noticedFallback) {
154
+ console.log(dim(`Render skipped (log too large: > ${MAX_RENDER_BYTES} bytes). Showing raw text.`));
155
+ liveRenderState.noticedFallback = true;
156
+ }
157
+ liveRenderState.fallback = true;
158
+ process.stdout.write(candidate + liveRenderState.pending);
159
+ liveRenderState.pending = '';
160
+ return;
161
+ }
162
+ process.stdout.write(renderMarkdownAnsi(candidate));
163
+ liveRenderState.renderedBytes += Buffer.byteLength(candidate, 'utf8');
164
+ }
165
+ };
166
+ const flushRemainder = () => {
167
+ if (!liveRenderState || liveRenderState.fallback) {
168
+ return;
169
+ }
170
+ if (liveRenderState.pending.length === 0) {
171
+ return;
172
+ }
173
+ const text = liveRenderState.pending;
174
+ liveRenderState.pending = '';
175
+ const projected = liveRenderState.renderedBytes + Buffer.byteLength(text, 'utf8');
176
+ if (projected > MAX_RENDER_BYTES) {
177
+ if (!liveRenderState.noticedFallback) {
178
+ console.log(dim(`Render skipped (log too large: > ${MAX_RENDER_BYTES} bytes). Showing raw text.`));
179
+ }
180
+ process.stdout.write(text);
181
+ liveRenderState.fallback = true;
182
+ return;
183
+ }
184
+ process.stdout.write(renderMarkdownAnsi(text));
185
+ };
82
186
  const printNew = async () => {
83
187
  const text = await readSessionLog(sessionId);
84
188
  const nextChunk = text.slice(lastLength);
85
189
  if (nextChunk.length > 0) {
86
- process.stdout.write(nextChunk);
190
+ renderLiveChunk(nextChunk);
87
191
  lastLength = text.length;
88
192
  }
89
193
  };
@@ -96,14 +200,21 @@ export async function attachSession(sessionId, options) {
96
200
  }
97
201
  if (latest.status === 'completed' || latest.status === 'error') {
98
202
  await printNew();
203
+ flushRemainder();
99
204
  if (!options?.suppressMetadata) {
100
205
  if (latest.status === 'error' && latest.errorMessage) {
101
206
  console.log('\nResult:');
102
207
  console.log(`Session failed: ${latest.errorMessage}`);
103
208
  }
104
- if (latest.usage && initialStatus === 'running') {
105
- const usage = latest.usage;
106
- console.log(`\nFinished (tok i/o/r/t: ${usage.inputTokens}/${usage.outputTokens}/${usage.reasoningTokens}/${usage.totalTokens})`);
209
+ if (latest.status === 'completed' && latest.usage) {
210
+ const summary = formatCompletionSummary(latest, { includeSlug: true });
211
+ if (summary) {
212
+ console.log(`\n${chalk.green.bold(summary)}`);
213
+ }
214
+ else {
215
+ const usage = latest.usage;
216
+ console.log(`\nFinished (tok i/o/r/t: ${usage.inputTokens}/${usage.outputTokens}/${usage.reasoningTokens}/${usage.totalTokens})`);
217
+ }
107
218
  }
108
219
  }
109
220
  break;
@@ -234,3 +345,104 @@ function printStatusExamples() {
234
345
  console.log(dim(' Attach to a specific running/completed session to stream its output.'));
235
346
  console.log(dim(CLEANUP_TIP));
236
347
  }
348
+ function extractRenderableChunks(text, state) {
349
+ const chunks = [];
350
+ let buffer = '';
351
+ const lines = text.split(/(\n)/);
352
+ for (let i = 0; i < lines.length; i += 1) {
353
+ const segment = lines[i];
354
+ if (segment === '\n') {
355
+ buffer += segment;
356
+ // Detect code fences
357
+ const prev = lines[i - 1] ?? '';
358
+ const fenceMatch = prev.match(/^(\s*)(`{3,}|~{3,})(.*)$/);
359
+ if (!state.inFence && fenceMatch) {
360
+ state.inFence = true;
361
+ state.fenceDelimiter = fenceMatch[2];
362
+ }
363
+ else if (state.inFence && state.fenceDelimiter && prev.startsWith(state.fenceDelimiter)) {
364
+ state.inFence = false;
365
+ state.fenceDelimiter = undefined;
366
+ }
367
+ const trimmed = prev.trim();
368
+ if (!state.inFence) {
369
+ if (!state.inTable && trimmed.startsWith('|') && trimmed.includes('|')) {
370
+ state.inTable = true;
371
+ }
372
+ if (state.inTable && trimmed === '') {
373
+ state.inTable = false;
374
+ }
375
+ }
376
+ const safeBreak = !state.inFence && !state.inTable && trimmed === '';
377
+ if (safeBreak) {
378
+ chunks.push(buffer);
379
+ buffer = '';
380
+ }
381
+ continue;
382
+ }
383
+ buffer += segment;
384
+ }
385
+ return { chunks, remainder: buffer };
386
+ }
387
+ function formatTimestamp(iso) {
388
+ const date = new Date(iso);
389
+ const locale = 'en-US';
390
+ const opts = {
391
+ year: 'numeric',
392
+ month: '2-digit',
393
+ day: '2-digit',
394
+ hour: 'numeric',
395
+ minute: '2-digit',
396
+ second: undefined,
397
+ hour12: true,
398
+ };
399
+ const formatted = date.toLocaleString(locale, opts);
400
+ return formatted.replace(/(, )(\d:)/, '$1 $2');
401
+ }
402
+ export function formatCompletionSummary(metadata, options = {}) {
403
+ if (!metadata.usage || metadata.elapsedMs == null) {
404
+ return null;
405
+ }
406
+ const modeLabel = metadata.mode === 'browser' ? `${metadata.model ?? 'n/a'}[browser]` : metadata.model ?? 'n/a';
407
+ const usage = metadata.usage;
408
+ const cost = metadata.mode === 'browser' ? null : resolveCost(metadata);
409
+ const costPart = cost != null ? ` | ${formatUSD(cost)}` : '';
410
+ const tokensDisplay = `${usage.inputTokens}/${usage.outputTokens}/${usage.reasoningTokens}/${usage.totalTokens}`;
411
+ const filesCount = metadata.options?.file?.length ?? 0;
412
+ const filesPart = filesCount > 0 ? ` | files=${filesCount}` : '';
413
+ const slugPart = options.includeSlug ? ` | slug=${metadata.id}` : '';
414
+ return `Finished in ${formatElapsed(metadata.elapsedMs)} (${modeLabel}${costPart} | tok(i/o/r/t)=${tokensDisplay}${filesPart}${slugPart})`;
415
+ }
416
+ function resolveCost(metadata) {
417
+ if (metadata.mode === 'browser') {
418
+ return null;
419
+ }
420
+ if (metadata.usage?.cost != null) {
421
+ return metadata.usage.cost;
422
+ }
423
+ if (!metadata.model || !metadata.usage) {
424
+ return null;
425
+ }
426
+ const pricing = MODEL_CONFIGS[metadata.model]?.pricing;
427
+ if (!pricing) {
428
+ return null;
429
+ }
430
+ const input = metadata.usage.inputTokens ?? 0;
431
+ const output = metadata.usage.outputTokens ?? 0;
432
+ const cost = input * pricing.inputPerToken + output * pricing.outputPerToken;
433
+ return cost > 0 ? cost : null;
434
+ }
435
+ function formatCostTable(cost) {
436
+ return `$${cost.toFixed(3)}`.padStart(7);
437
+ }
438
+ async function readStoredPrompt(sessionId) {
439
+ const request = await readSessionRequest(sessionId);
440
+ if (request?.prompt && request.prompt.trim().length > 0) {
441
+ return request.prompt;
442
+ }
443
+ const meta = await readSessionMetadata(sessionId);
444
+ if (meta?.options?.prompt && meta.options.prompt.trim().length > 0) {
445
+ return meta.options.prompt;
446
+ }
447
+ return null;
448
+ }
@@ -1,18 +1,20 @@
1
1
  import kleur from 'kleur';
2
2
  import { updateSessionMetadata } from '../sessionManager.js';
3
- import { runOracle, OracleResponseError, OracleTransportError, extractResponseMetadata, asOracleUserError, } from '../oracle.js';
3
+ import { runOracle, OracleResponseError, OracleTransportError, extractResponseMetadata, asOracleUserError, extractTextOutput, } from '../oracle.js';
4
4
  import { runBrowserSessionExecution } from '../browser/sessionRunner.js';
5
5
  import { formatResponseMetadata, formatTransportMetadata } from './sessionDisplay.js';
6
6
  import { markErrorLogged } from './errorUtils.js';
7
+ import { sendSessionNotification, deriveNotificationSettingsFromMetadata, } from './notifier.js';
7
8
  const isTty = process.stdout.isTTY;
8
9
  const dim = (text) => (isTty ? kleur.dim(text) : text);
9
- export async function performSessionRun({ sessionMeta, runOptions, mode, browserConfig, cwd, log, write, version, }) {
10
+ export async function performSessionRun({ sessionMeta, runOptions, mode, browserConfig, cwd, log, write, version, notifications, }) {
10
11
  await updateSessionMetadata(sessionMeta.id, {
11
12
  status: 'running',
12
13
  startedAt: new Date().toISOString(),
13
14
  mode,
14
15
  ...(browserConfig ? { browser: { config: browserConfig } } : {}),
15
16
  });
17
+ const notificationSettings = notifications ?? deriveNotificationSettingsFromMetadata(sessionMeta, process.env);
16
18
  try {
17
19
  if (mode === 'browser') {
18
20
  if (!browserConfig) {
@@ -32,6 +34,14 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
32
34
  transport: undefined,
33
35
  error: undefined,
34
36
  });
37
+ await sendSessionNotification({
38
+ sessionId: sessionMeta.id,
39
+ sessionName: sessionMeta.options?.slug ?? sessionMeta.id,
40
+ mode,
41
+ model: sessionMeta.model,
42
+ usage: result.usage,
43
+ characters: result.answerText?.length,
44
+ }, notificationSettings, log, result.answerText?.slice(0, 140));
35
45
  return;
36
46
  }
37
47
  const result = await runOracle(runOptions, {
@@ -51,6 +61,15 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
51
61
  transport: undefined,
52
62
  error: undefined,
53
63
  });
64
+ const answerText = extractTextOutput(result.response);
65
+ await sendSessionNotification({
66
+ sessionId: sessionMeta.id,
67
+ sessionName: sessionMeta.options?.slug ?? sessionMeta.id,
68
+ mode,
69
+ model: sessionMeta.model ?? runOptions.model,
70
+ usage: result.usage,
71
+ characters: answerText.length,
72
+ }, notificationSettings, log, answerText.slice(0, 140));
54
73
  }
55
74
  catch (error) {
56
75
  const message = formatError(error);