@steipete/oracle 0.16.1 → 0.17.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/README.md +72 -337
- package/dist/bin/oracle-cli.js +184 -55
- package/dist/docs-site/.nojekyll +0 -0
- package/dist/docs-site/CNAME +1 -0
- package/dist/docs-site/RELEASING.html +410 -0
- package/dist/docs-site/agents.html +374 -0
- package/dist/docs-site/anthropic.html +368 -0
- package/dist/docs-site/bridge.html +416 -0
- package/dist/docs-site/browser-mode.html +594 -0
- package/dist/docs-site/chromium-forks.html +347 -0
- package/dist/docs-site/cli-reference.html +346 -0
- package/dist/docs-site/configuration.html +462 -0
- package/dist/docs-site/favicon.svg +14 -0
- package/dist/docs-site/followup.html +375 -0
- package/dist/docs-site/gemini.html +383 -0
- package/dist/docs-site/grok.html +325 -0
- package/dist/docs-site/index.html +360 -0
- package/dist/docs-site/install.html +335 -0
- package/dist/docs-site/linux.html +321 -0
- package/dist/docs-site/llms.txt +43 -0
- package/dist/docs-site/manual-tests.html +596 -0
- package/dist/docs-site/mcp.html +391 -0
- package/dist/docs-site/multimodel.html +364 -0
- package/dist/docs-site/mythical-pro-agents.html +360 -0
- package/dist/docs-site/notifier.html +338 -0
- package/dist/docs-site/openai-endpoints.html +410 -0
- package/dist/docs-site/openrouter.html +344 -0
- package/dist/docs-site/quickstart.html +369 -0
- package/dist/docs-site/refactor/ux.html +532 -0
- package/dist/docs-site/sessions.html +389 -0
- package/dist/docs-site/social-card.png +0 -0
- package/dist/docs-site/social-card.svg +79 -0
- package/dist/docs-site/spec.html +363 -0
- package/dist/docs-site/testing.html +320 -0
- package/dist/docs-site/tui-debug.html +326 -0
- package/dist/docs-site/windows-work.html +324 -0
- package/dist/docs-site/windows.html +320 -0
- package/dist/scripts/test-browser.js +1 -25
- package/dist/src/browser/actions/modelSelection.js +36 -34
- package/dist/src/browser/actions/navigation.js +8 -3
- package/dist/src/browser/actions/thinkingTime.js +39 -3
- package/dist/src/browser/chromeLifecycle.js +2 -37
- package/dist/src/browser/index.js +23 -8
- package/dist/src/browser/modelDisplay.js +67 -0
- package/dist/src/browser/reattach.js +14 -1
- package/dist/src/browser/recoverConversation.js +2 -1
- package/dist/src/browser/sessionRunner.js +9 -10
- package/dist/src/browser/wslHost.js +50 -0
- package/dist/src/cli/browserConfig.js +31 -11
- package/dist/src/cli/detach.js +21 -4
- package/dist/src/cli/dryRun.js +13 -2
- package/dist/src/cli/engine.js +2 -2
- package/dist/src/cli/options.js +4 -0
- package/dist/src/cli/sessionDisplay.js +60 -8
- package/dist/src/cli/sessionLifecycle.js +2 -1
- package/dist/src/cli/sessionRunner.js +110 -60
- package/dist/src/cli/sessionTable.js +5 -1
- package/dist/src/cli/tui/index.js +12 -4
- package/dist/src/duration.js +3 -0
- package/dist/src/gemini-web/client.js +19 -1
- package/dist/src/oracle/modelResolver.js +8 -1
- package/dist/src/oracle/request.js +9 -2
- package/dist/src/oracle/run.js +43 -3
- package/dist/src/sessionManager.js +41 -12
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/package.json +15 -15
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
|
@@ -12,6 +12,7 @@ import { estimateTokenCount } from "../browser/utils.js";
|
|
|
12
12
|
import { formatSessionTableHeader, formatSessionTableRow, resolveSessionCost, } from "./sessionTable.js";
|
|
13
13
|
import { abbreviateResponseId, buildResponseOwnerIndex, resolveSessionLineage, } from "./sessionLineage.js";
|
|
14
14
|
import { formatSessionExecutionLabel } from "./sessionLifecycle.js";
|
|
15
|
+
import { formatBrowserModelSelectionEvidence, formatSessionBrowserModelWithRequestedKey, resolveSessionBrowserModelDisplayName, } from "../browser/modelDisplay.js";
|
|
15
16
|
const isTty = () => Boolean(process.stdout.isTTY);
|
|
16
17
|
const dim = (text) => (isTty() ? kleur.dim(text) : text);
|
|
17
18
|
export const MAX_RENDER_BYTES = 200_000;
|
|
@@ -185,6 +186,7 @@ export async function attachSession(sessionId, options) {
|
|
|
185
186
|
const isVerbose = Boolean(process.env.ORACLE_VERBOSE_RENDER);
|
|
186
187
|
const runtime = metadata.browser?.runtime;
|
|
187
188
|
const controllerAlive = isProcessAlive(runtime?.controllerPid);
|
|
189
|
+
const workerAlive = isProcessAlive(metadata.lifecycle?.workerPid);
|
|
188
190
|
const hasChromeDisconnect = metadata.response?.incompleteReason === "chrome-disconnected";
|
|
189
191
|
const hasIncompleteCapture = metadata.response?.incompleteReason === "incomplete-capture";
|
|
190
192
|
const statusAllowsReattach = metadata.status === "running" ||
|
|
@@ -204,6 +206,7 @@ export async function attachSession(sessionId, options) {
|
|
|
204
206
|
const canReattach = (statusAllowsReattach || completedDeepResearchPlaceholder) &&
|
|
205
207
|
metadata.mode === "browser" &&
|
|
206
208
|
hasFallbackSessionInfo &&
|
|
209
|
+
!workerAlive &&
|
|
207
210
|
(hasRecoverableConversation ||
|
|
208
211
|
runtime?.promptSubmitted ||
|
|
209
212
|
hasLiveChromeFallback ||
|
|
@@ -311,11 +314,17 @@ export async function attachSession(sessionId, options) {
|
|
|
311
314
|
const usage = run.usage
|
|
312
315
|
? ` tok=${formatTokenCount(run.usage.outputTokens ?? 0)}/${formatTokenCount(run.usage.totalTokens ?? 0)}`
|
|
313
316
|
: "";
|
|
314
|
-
|
|
317
|
+
const modelLabel = (metadata.mode ?? metadata.options?.mode) === "browser"
|
|
318
|
+
? formatSessionBrowserModelWithRequestedKey(metadata, run.model)
|
|
319
|
+
: run.model;
|
|
320
|
+
console.log(`- ${chalk.cyan(modelLabel)} — ${run.status}${usage}`);
|
|
315
321
|
}
|
|
316
322
|
}
|
|
317
323
|
else if (metadata.model) {
|
|
318
|
-
|
|
324
|
+
const modelLabel = (metadata.mode ?? metadata.options?.mode) === "browser"
|
|
325
|
+
? formatSessionBrowserModelWithRequestedKey(metadata)
|
|
326
|
+
: metadata.model;
|
|
327
|
+
console.log(`Model: ${modelLabel}`);
|
|
319
328
|
}
|
|
320
329
|
const browserEvidence = formatBrowserEvidence(metadata);
|
|
321
330
|
if (browserEvidence) {
|
|
@@ -391,6 +400,9 @@ export async function attachSession(sessionId, options) {
|
|
|
391
400
|
if (summary) {
|
|
392
401
|
console.log(`\n${chalk.green.bold(summary)}`);
|
|
393
402
|
}
|
|
403
|
+
if (options?.propagateFailure && metadata.status === "error") {
|
|
404
|
+
process.exitCode = 1;
|
|
405
|
+
}
|
|
394
406
|
return;
|
|
395
407
|
}
|
|
396
408
|
if (wantsRender) {
|
|
@@ -493,6 +505,48 @@ export async function attachSession(sessionId, options) {
|
|
|
493
505
|
}
|
|
494
506
|
}
|
|
495
507
|
}
|
|
508
|
+
if (options?.propagateFailure && latest.status === "error") {
|
|
509
|
+
process.exitCode = 1;
|
|
510
|
+
}
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
const controllerPid = latest.lifecycle?.workerPid ?? latest.browser?.runtime?.controllerPid;
|
|
514
|
+
if (latest.lifecycle?.detached && controllerPid && !isProcessAlive(controllerPid)) {
|
|
515
|
+
const settled = await sessionStore.readSession(sessionId);
|
|
516
|
+
if (!settled) {
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
if (settled.status === "completed" || settled.status === "partial") {
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
await printNew();
|
|
523
|
+
flushRemainder();
|
|
524
|
+
const message = settled.status === "error"
|
|
525
|
+
? (settled.errorMessage ?? "Detached worker failed.")
|
|
526
|
+
: "Detached worker exited before the session reached a terminal state.";
|
|
527
|
+
const failure = {
|
|
528
|
+
category: "internal",
|
|
529
|
+
message,
|
|
530
|
+
};
|
|
531
|
+
if (settled.model) {
|
|
532
|
+
await sessionStore.updateModelRun(settled.id, settled.model, {
|
|
533
|
+
status: "error",
|
|
534
|
+
completedAt: new Date().toISOString(),
|
|
535
|
+
response: { status: "incomplete", incompleteReason: "incomplete-capture" },
|
|
536
|
+
error: failure,
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
await sessionStore.updateSession(settled.id, {
|
|
540
|
+
status: "error",
|
|
541
|
+
completedAt: new Date().toISOString(),
|
|
542
|
+
errorMessage: message,
|
|
543
|
+
response: { status: "incomplete", incompleteReason: "incomplete-capture" },
|
|
544
|
+
error: failure,
|
|
545
|
+
});
|
|
546
|
+
console.log(chalk.yellow(`${message} Reattach via: ${settled.lifecycle?.reattachCommand}`));
|
|
547
|
+
if (options?.propagateFailure) {
|
|
548
|
+
process.exitCode = 1;
|
|
549
|
+
}
|
|
496
550
|
break;
|
|
497
551
|
}
|
|
498
552
|
await wait(1000);
|
|
@@ -555,11 +609,7 @@ export function formatBrowserEvidence(metadata) {
|
|
|
555
609
|
const lines = [];
|
|
556
610
|
const evidence = browser.modelSelection;
|
|
557
611
|
if (evidence) {
|
|
558
|
-
|
|
559
|
-
const resolved = evidence.resolvedLabel ?? "(unavailable)";
|
|
560
|
-
const strategy = evidence.strategy ?? "(default)";
|
|
561
|
-
const verified = evidence.verified ? "yes" : "no";
|
|
562
|
-
lines.push(`model requested=${requested}; resolved=${resolved}; status=${evidence.status}; strategy=${strategy}; verified=${verified}`);
|
|
612
|
+
lines.push(`model ${formatBrowserModelSelectionEvidence(evidence, metadata.model)}`);
|
|
563
613
|
}
|
|
564
614
|
for (const warning of browser.warnings ?? []) {
|
|
565
615
|
lines.push(`warning ${warning.code}: ${warning.message}`);
|
|
@@ -821,7 +871,9 @@ export function formatCompletionSummary(metadata, options = {}) {
|
|
|
821
871
|
if (!metadata.usage || metadata.elapsedMs == null) {
|
|
822
872
|
return null;
|
|
823
873
|
}
|
|
824
|
-
const modeLabel = metadata.mode
|
|
874
|
+
const modeLabel = (metadata.mode ?? metadata.options?.mode) === "browser"
|
|
875
|
+
? `${resolveSessionBrowserModelDisplayName(metadata)}[browser]`
|
|
876
|
+
: (metadata.model ?? "n/a");
|
|
825
877
|
const usage = metadata.usage;
|
|
826
878
|
const cost = resolveSessionCost(metadata);
|
|
827
879
|
const tokensDisplay = [
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
export function buildSessionLifecycle({ engine, detached, reattachCommand, }) {
|
|
1
|
+
export function buildSessionLifecycle({ engine, detached, workerPid, reattachCommand, }) {
|
|
2
2
|
return {
|
|
3
3
|
engine,
|
|
4
4
|
execution: detached ? "background" : "foreground",
|
|
5
5
|
attached: !detached,
|
|
6
6
|
detached,
|
|
7
|
+
workerPid,
|
|
7
8
|
reattachCommand,
|
|
8
9
|
};
|
|
9
10
|
}
|
|
@@ -78,6 +78,15 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
78
78
|
cwd,
|
|
79
79
|
log,
|
|
80
80
|
}, runnerDeps);
|
|
81
|
+
await writeAssistantOutput(runOptions.writeOutputPath, result.answerText ?? "", log);
|
|
82
|
+
await sendSessionNotification({
|
|
83
|
+
sessionId: sessionMeta.id,
|
|
84
|
+
sessionName: sessionMeta.options?.slug ?? sessionMeta.id,
|
|
85
|
+
mode,
|
|
86
|
+
model: sessionMeta.model,
|
|
87
|
+
usage: result.usage,
|
|
88
|
+
characters: result.answerText?.length,
|
|
89
|
+
}, notificationSettings, log, result.answerText?.slice(0, 140));
|
|
81
90
|
if (modelForStatus) {
|
|
82
91
|
await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
|
|
83
92
|
status: "completed",
|
|
@@ -103,15 +112,6 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
103
112
|
transport: undefined,
|
|
104
113
|
error: undefined,
|
|
105
114
|
});
|
|
106
|
-
await writeAssistantOutput(runOptions.writeOutputPath, result.answerText ?? "", log);
|
|
107
|
-
await sendSessionNotification({
|
|
108
|
-
sessionId: sessionMeta.id,
|
|
109
|
-
sessionName: sessionMeta.options?.slug ?? sessionMeta.id,
|
|
110
|
-
mode,
|
|
111
|
-
model: sessionMeta.model,
|
|
112
|
-
usage: result.usage,
|
|
113
|
-
characters: result.answerText?.length,
|
|
114
|
-
}, notificationSettings, log, result.answerText?.slice(0, 140));
|
|
115
115
|
return;
|
|
116
116
|
}
|
|
117
117
|
const multiModels = Array.isArray(runOptions.models) ? runOptions.models.filter(Boolean) : [];
|
|
@@ -362,16 +362,6 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
362
362
|
if (result.mode !== "live") {
|
|
363
363
|
throw new Error("Unexpected preview result while running a session.");
|
|
364
364
|
}
|
|
365
|
-
await sessionStore.updateSession(sessionMeta.id, {
|
|
366
|
-
status: "completed",
|
|
367
|
-
completedAt: new Date().toISOString(),
|
|
368
|
-
usage: result.usage,
|
|
369
|
-
elapsedMs: result.elapsedMs,
|
|
370
|
-
errorMessage: undefined,
|
|
371
|
-
response: extractResponseMetadata(result.response),
|
|
372
|
-
transport: undefined,
|
|
373
|
-
error: undefined,
|
|
374
|
-
});
|
|
375
365
|
if (modelForStatus && singleModelOverride == null) {
|
|
376
366
|
await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
|
|
377
367
|
status: "completed",
|
|
@@ -389,6 +379,16 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
389
379
|
usage: result.usage,
|
|
390
380
|
characters: answerText.length,
|
|
391
381
|
}, notificationSettings, log, answerText.slice(0, 140));
|
|
382
|
+
await sessionStore.updateSession(sessionMeta.id, {
|
|
383
|
+
status: "completed",
|
|
384
|
+
completedAt: new Date().toISOString(),
|
|
385
|
+
usage: result.usage,
|
|
386
|
+
elapsedMs: result.elapsedMs,
|
|
387
|
+
errorMessage: undefined,
|
|
388
|
+
response: extractResponseMetadata(result.response),
|
|
389
|
+
transport: undefined,
|
|
390
|
+
error: undefined,
|
|
391
|
+
});
|
|
392
392
|
}
|
|
393
393
|
catch (error) {
|
|
394
394
|
const message = formatError(error);
|
|
@@ -510,16 +510,60 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
510
510
|
const runtime = userError.details
|
|
511
511
|
?.runtime;
|
|
512
512
|
log(dim("Assistant response timed out; marking capture incomplete for reattach."));
|
|
513
|
+
const timeoutResponse = {
|
|
514
|
+
status: "incomplete",
|
|
515
|
+
incompleteReason: "incomplete-capture",
|
|
516
|
+
};
|
|
517
|
+
const timeoutError = {
|
|
518
|
+
category: userError.category,
|
|
519
|
+
message: userError.message,
|
|
520
|
+
details: userError.details,
|
|
521
|
+
};
|
|
522
|
+
const autoReattachIntervalMs = browserConfig?.autoReattachIntervalMs ?? 0;
|
|
523
|
+
const autoRuntime = runtime ?? currentBrowser?.runtime;
|
|
524
|
+
const willAutoReattach = autoReattachIntervalMs > 0 && Boolean(autoRuntime);
|
|
525
|
+
if (willAutoReattach) {
|
|
526
|
+
if (modelForStatus) {
|
|
527
|
+
await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
|
|
528
|
+
status: "running",
|
|
529
|
+
completedAt: undefined,
|
|
530
|
+
response: timeoutResponse,
|
|
531
|
+
error: timeoutError,
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
await sessionStore.updateSession(sessionMeta.id, {
|
|
535
|
+
status: "running",
|
|
536
|
+
completedAt: undefined,
|
|
537
|
+
errorMessage: message,
|
|
538
|
+
mode,
|
|
539
|
+
browser: {
|
|
540
|
+
...currentBrowser,
|
|
541
|
+
config: browserConfig,
|
|
542
|
+
runtime: autoRuntime,
|
|
543
|
+
},
|
|
544
|
+
response: timeoutResponse,
|
|
545
|
+
error: timeoutError,
|
|
546
|
+
});
|
|
547
|
+
const success = await autoReattachUntilComplete({
|
|
548
|
+
sessionMeta,
|
|
549
|
+
runtime: autoRuntime,
|
|
550
|
+
browserConfig,
|
|
551
|
+
browserMetadata: currentBrowser,
|
|
552
|
+
runOptions,
|
|
553
|
+
modelForStatus,
|
|
554
|
+
notificationSettings,
|
|
555
|
+
log,
|
|
556
|
+
});
|
|
557
|
+
if (success) {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
513
561
|
if (modelForStatus) {
|
|
514
562
|
await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
|
|
515
563
|
status: "error",
|
|
516
564
|
completedAt: new Date().toISOString(),
|
|
517
|
-
response:
|
|
518
|
-
error:
|
|
519
|
-
category: userError.category,
|
|
520
|
-
message: userError.message,
|
|
521
|
-
details: userError.details,
|
|
522
|
-
},
|
|
565
|
+
response: timeoutResponse,
|
|
566
|
+
error: timeoutError,
|
|
523
567
|
});
|
|
524
568
|
}
|
|
525
569
|
await sessionStore.updateSession(sessionMeta.id, {
|
|
@@ -532,30 +576,9 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
532
576
|
config: browserConfig,
|
|
533
577
|
runtime: runtime ?? currentBrowser?.runtime,
|
|
534
578
|
},
|
|
535
|
-
response:
|
|
536
|
-
error:
|
|
537
|
-
category: userError.category,
|
|
538
|
-
message: userError.message,
|
|
539
|
-
details: userError.details,
|
|
540
|
-
},
|
|
579
|
+
response: timeoutResponse,
|
|
580
|
+
error: timeoutError,
|
|
541
581
|
});
|
|
542
|
-
const autoReattachIntervalMs = browserConfig?.autoReattachIntervalMs ?? 0;
|
|
543
|
-
if (autoReattachIntervalMs > 0) {
|
|
544
|
-
const autoRuntime = runtime ?? currentBrowser?.runtime;
|
|
545
|
-
const success = await autoReattachUntilComplete({
|
|
546
|
-
sessionMeta,
|
|
547
|
-
runtime: autoRuntime ?? undefined,
|
|
548
|
-
browserConfig,
|
|
549
|
-
browserMetadata: currentBrowser,
|
|
550
|
-
runOptions,
|
|
551
|
-
modelForStatus,
|
|
552
|
-
notificationSettings,
|
|
553
|
-
log,
|
|
554
|
-
});
|
|
555
|
-
if (success) {
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
582
|
logBrowserReattachGuidance(runtime ?? currentBrowser?.runtime);
|
|
560
583
|
return;
|
|
561
584
|
}
|
|
@@ -911,6 +934,7 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
|
|
|
911
934
|
}
|
|
912
935
|
attempt += 1;
|
|
913
936
|
log(dim(`Auto-reattach attempt ${attempt}...`));
|
|
937
|
+
let captureSucceeded = false;
|
|
914
938
|
try {
|
|
915
939
|
const reattachConfig = {
|
|
916
940
|
...browserConfig,
|
|
@@ -919,6 +943,7 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
|
|
|
919
943
|
const result = await resumeBrowserSession(runtime, reattachConfig, logger, {
|
|
920
944
|
promptPreview: sessionMeta.promptPreview,
|
|
921
945
|
});
|
|
946
|
+
captureSucceeded = true;
|
|
922
947
|
const answerText = result.answerMarkdown || result.answerText || "";
|
|
923
948
|
const outputTokens = estimateTokenCount(answerText);
|
|
924
949
|
const artifacts = await ensureSessionArtifacts({
|
|
@@ -947,6 +972,18 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
|
|
|
947
972
|
},
|
|
948
973
|
});
|
|
949
974
|
}
|
|
975
|
+
await writeAssistantOutput(runOptions.writeOutputPath, answerText, log);
|
|
976
|
+
await sendSessionNotification({
|
|
977
|
+
sessionId: sessionMeta.id,
|
|
978
|
+
sessionName: sessionMeta.options?.slug ?? sessionMeta.id,
|
|
979
|
+
mode: sessionMeta.mode ?? "browser",
|
|
980
|
+
model: sessionMeta.model ?? runOptions.model,
|
|
981
|
+
usage: {
|
|
982
|
+
inputTokens: 0,
|
|
983
|
+
outputTokens,
|
|
984
|
+
},
|
|
985
|
+
characters: answerText.length,
|
|
986
|
+
}, notificationSettings, log, answerText.slice(0, 140));
|
|
950
987
|
await sessionStore.updateSession(sessionMeta.id, {
|
|
951
988
|
status: "completed",
|
|
952
989
|
completedAt: new Date().toISOString(),
|
|
@@ -967,22 +1004,35 @@ async function autoReattachUntilComplete({ sessionMeta, runtime, browserConfig,
|
|
|
967
1004
|
error: undefined,
|
|
968
1005
|
transport: undefined,
|
|
969
1006
|
});
|
|
970
|
-
await writeAssistantOutput(runOptions.writeOutputPath, answerText, log);
|
|
971
|
-
await sendSessionNotification({
|
|
972
|
-
sessionId: sessionMeta.id,
|
|
973
|
-
sessionName: sessionMeta.options?.slug ?? sessionMeta.id,
|
|
974
|
-
mode: sessionMeta.mode ?? "browser",
|
|
975
|
-
model: sessionMeta.model ?? runOptions.model,
|
|
976
|
-
usage: {
|
|
977
|
-
inputTokens: 0,
|
|
978
|
-
outputTokens,
|
|
979
|
-
},
|
|
980
|
-
characters: answerText.length,
|
|
981
|
-
}, notificationSettings, log, answerText.slice(0, 140));
|
|
982
1007
|
log(kleur.green("Auto-reattach succeeded; session marked completed."));
|
|
983
1008
|
return true;
|
|
984
1009
|
}
|
|
985
1010
|
catch (error) {
|
|
1011
|
+
if (captureSucceeded) {
|
|
1012
|
+
const message = formatError(error);
|
|
1013
|
+
if (modelForStatus) {
|
|
1014
|
+
await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
|
|
1015
|
+
status: "error",
|
|
1016
|
+
completedAt: new Date().toISOString(),
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
await sessionStore.updateSession(sessionMeta.id, {
|
|
1020
|
+
status: "error",
|
|
1021
|
+
completedAt: new Date().toISOString(),
|
|
1022
|
+
errorMessage: message,
|
|
1023
|
+
browser: {
|
|
1024
|
+
...browserMetadata,
|
|
1025
|
+
config: browserConfig,
|
|
1026
|
+
runtime,
|
|
1027
|
+
},
|
|
1028
|
+
response: { status: "error", incompleteReason: "incomplete-capture" },
|
|
1029
|
+
error: {
|
|
1030
|
+
category: "internal",
|
|
1031
|
+
message,
|
|
1032
|
+
},
|
|
1033
|
+
});
|
|
1034
|
+
throw error;
|
|
1035
|
+
}
|
|
986
1036
|
const message = error instanceof Error ? error.message : String(error);
|
|
987
1037
|
log(dim(`Auto-reattach attempt ${attempt} failed: ${message}`));
|
|
988
1038
|
}
|
|
@@ -3,6 +3,7 @@ import kleur from "kleur";
|
|
|
3
3
|
import { MODEL_CONFIGS } from "../oracle.js";
|
|
4
4
|
import { estimateUsdCost } from "tokentally";
|
|
5
5
|
import { formatSessionExecutionLabel } from "./sessionLifecycle.js";
|
|
6
|
+
import { resolveSessionBrowserModelDisplayName } from "../browser/modelDisplay.js";
|
|
6
7
|
const isRich = (rich) => rich ?? Boolean(process.stdout.isTTY && chalk.level > 0);
|
|
7
8
|
const dim = (text, rich) => (rich ? kleur.dim(text) : text);
|
|
8
9
|
export const STATUS_PAD = 9;
|
|
@@ -18,7 +19,10 @@ export function formatSessionTableHeader(rich) {
|
|
|
18
19
|
export function formatSessionTableRow(meta, options) {
|
|
19
20
|
const rich = isRich(options?.rich);
|
|
20
21
|
const status = colorStatus(meta.status ?? "unknown", rich);
|
|
21
|
-
const
|
|
22
|
+
const displayModel = (meta.mode ?? meta.options?.mode) === "browser"
|
|
23
|
+
? resolveSessionBrowserModelDisplayName(meta)
|
|
24
|
+
: (meta.model ?? "n/a");
|
|
25
|
+
const modelLabel = displayModel.padEnd(MODEL_PAD);
|
|
22
26
|
const model = rich ? chalk.white(modelLabel) : modelLabel;
|
|
23
27
|
const modeLabel = formatSessionExecutionLabel(meta).padEnd(MODE_PAD);
|
|
24
28
|
const mode = rich ? chalk.gray(modeLabel) : modeLabel;
|
|
@@ -15,6 +15,7 @@ import { resolveNotificationSettings } from "../notifier.js";
|
|
|
15
15
|
import { loadUserConfig } from "../../config.js";
|
|
16
16
|
import { resolveConfiguredMaxFileSizeBytes } from "../fileSize.js";
|
|
17
17
|
import { formatTokenCount } from "../../oracle/runUtils.js";
|
|
18
|
+
import { formatSessionBrowserModelWithRequestedKey, resolveSessionBrowserModelDisplayName, } from "../../browser/modelDisplay.js";
|
|
18
19
|
const isTty = () => Boolean(process.stdout.isTTY && chalk.level > 0);
|
|
19
20
|
const dim = (text) => (isTty() ? kleur.dim(text) : text);
|
|
20
21
|
const RECENT_WINDOW_HOURS = 24;
|
|
@@ -157,7 +158,7 @@ async function showSessionDetail(sessionId) {
|
|
|
157
158
|
console.clear();
|
|
158
159
|
printSessionHeader(meta);
|
|
159
160
|
if (meta.models && meta.models.length > 0) {
|
|
160
|
-
printModelSummaries(meta
|
|
161
|
+
printModelSummaries(meta);
|
|
161
162
|
}
|
|
162
163
|
const prompt = await readStoredPrompt(sessionId);
|
|
163
164
|
if (prompt) {
|
|
@@ -250,7 +251,10 @@ function printSessionHeader(meta) {
|
|
|
250
251
|
console.log(`${chalk.white("Status:")} ${meta.status}`);
|
|
251
252
|
console.log(`${chalk.white("Created:")} ${meta.createdAt}`);
|
|
252
253
|
if (meta.model) {
|
|
253
|
-
|
|
254
|
+
const modelLabel = (meta.mode ?? meta.options?.mode) === "browser"
|
|
255
|
+
? resolveSessionBrowserModelDisplayName(meta)
|
|
256
|
+
: meta.model;
|
|
257
|
+
console.log(`${chalk.white("Model:")} ${modelLabel}`);
|
|
254
258
|
}
|
|
255
259
|
const mode = meta.mode ?? meta.options?.mode;
|
|
256
260
|
if (mode) {
|
|
@@ -260,7 +264,8 @@ function printSessionHeader(meta) {
|
|
|
260
264
|
console.log(chalk.red(`Error: ${meta.errorMessage}`));
|
|
261
265
|
}
|
|
262
266
|
}
|
|
263
|
-
function printModelSummaries(
|
|
267
|
+
function printModelSummaries(meta) {
|
|
268
|
+
const models = meta.models ?? [];
|
|
264
269
|
if (models.length === 0) {
|
|
265
270
|
return;
|
|
266
271
|
}
|
|
@@ -269,7 +274,10 @@ function printModelSummaries(models) {
|
|
|
269
274
|
const usage = run.usage
|
|
270
275
|
? ` tok=${formatTokenCount(run.usage.outputTokens ?? 0)}/${formatTokenCount(run.usage.totalTokens ?? 0)}`
|
|
271
276
|
: "";
|
|
272
|
-
|
|
277
|
+
const modelLabel = (meta.mode ?? meta.options?.mode) === "browser"
|
|
278
|
+
? formatSessionBrowserModelWithRequestedKey(meta, run.model)
|
|
279
|
+
: run.model;
|
|
280
|
+
console.log(` - ${chalk.cyan(modelLabel)} — ${run.status}${usage}`);
|
|
273
281
|
}
|
|
274
282
|
console.log("");
|
|
275
283
|
}
|
package/dist/src/duration.js
CHANGED
|
@@ -21,6 +21,9 @@ export function parseDuration(input, fallback) {
|
|
|
21
21
|
let lastIndex = 0;
|
|
22
22
|
let match = multiDuration.exec(normalized);
|
|
23
23
|
while (match !== null) {
|
|
24
|
+
if (match.index !== lastIndex) {
|
|
25
|
+
return fallback;
|
|
26
|
+
}
|
|
24
27
|
total += convertUnit(Number(match[1]), match[2]);
|
|
25
28
|
lastIndex = multiDuration.lastIndex;
|
|
26
29
|
match = multiDuration.exec(normalized);
|
|
@@ -12,11 +12,29 @@ const GEMINI_UPLOAD_MIME_TYPES = {
|
|
|
12
12
|
".gif": "image/gif",
|
|
13
13
|
".jpeg": "image/jpeg",
|
|
14
14
|
".jpg": "image/jpeg",
|
|
15
|
+
".mov": "video/quicktime",
|
|
16
|
+
".mp4": "video/mp4",
|
|
15
17
|
".pdf": "application/pdf",
|
|
16
18
|
".png": "image/png",
|
|
17
19
|
".svg": "image/svg+xml",
|
|
20
|
+
".webm": "video/webm",
|
|
18
21
|
".webp": "image/webp",
|
|
19
22
|
};
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the MIME type Gemini should be told an upload carries.
|
|
25
|
+
*
|
|
26
|
+
* Gemini silently discards uploads it cannot type: the run still reports the file as
|
|
27
|
+
* attached, but the model never receives it and answers as though nothing was sent.
|
|
28
|
+
* Anything falling back to `application/octet-stream` is therefore invisible to the model.
|
|
29
|
+
*
|
|
30
|
+
* Only formats confirmed to work against the Gemini web upload endpoint are listed. The
|
|
31
|
+
* endpoint also gates on the file extension, not just the declared type — an `.m4v` byte
|
|
32
|
+
* for byte identical to a working `.mp4`, and declared `video/mp4`, is still dropped — so
|
|
33
|
+
* entries here cannot be extrapolated from what the Gemini API documents.
|
|
34
|
+
*/
|
|
35
|
+
export function resolveGeminiUploadMimeType(filePath) {
|
|
36
|
+
return (GEMINI_UPLOAD_MIME_TYPES[path.extname(filePath).toLowerCase()] ?? "application/octet-stream");
|
|
37
|
+
}
|
|
20
38
|
function getNestedValue(value, pathParts, fallback) {
|
|
21
39
|
let current = value;
|
|
22
40
|
for (const part of pathParts) {
|
|
@@ -125,7 +143,7 @@ async function uploadGeminiFile(filePath, signal) {
|
|
|
125
143
|
const absPath = path.resolve(process.cwd(), filePath);
|
|
126
144
|
const data = await readFile(absPath);
|
|
127
145
|
const fileName = path.basename(absPath);
|
|
128
|
-
const mimeType =
|
|
146
|
+
const mimeType = resolveGeminiUploadMimeType(absPath);
|
|
129
147
|
const form = new FormData();
|
|
130
148
|
form.append("file", new Blob([data], { type: mimeType }), fileName);
|
|
131
149
|
const res = await fetch(GEMINI_UPLOAD_URL, {
|
|
@@ -192,7 +192,14 @@ async function resolveBaseModelConfig(model, options = {}) {
|
|
|
192
192
|
export function isProModel(model) {
|
|
193
193
|
return isKnownModel(model) && PRO_MODELS.has(model);
|
|
194
194
|
}
|
|
195
|
-
const VALID_REASONING_EFFORTS = [
|
|
195
|
+
const VALID_REASONING_EFFORTS = [
|
|
196
|
+
"none",
|
|
197
|
+
"low",
|
|
198
|
+
"medium",
|
|
199
|
+
"high",
|
|
200
|
+
"xhigh",
|
|
201
|
+
"max",
|
|
202
|
+
];
|
|
196
203
|
function isRecord(value) {
|
|
197
204
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
198
205
|
}
|
|
@@ -11,8 +11,15 @@ export function buildPrompt(basePrompt, files, cwd = process.cwd()) {
|
|
|
11
11
|
const sectionText = formatFileSections(sections, { includeFileIndex: true });
|
|
12
12
|
return `${basePrompt.trim()}\n\n${sectionText}`;
|
|
13
13
|
}
|
|
14
|
-
export function buildRequestBody({ modelConfig, systemPrompt, userPrompt, searchEnabled, maxOutputTokens, background, storeResponse, previousResponseId, }) {
|
|
14
|
+
export function buildRequestBody({ modelConfig, reasoningEffort, reasoningMode, systemPrompt, userPrompt, searchEnabled, maxOutputTokens, background, storeResponse, previousResponseId, }) {
|
|
15
15
|
const searchToolType = modelConfig.searchToolType ?? "web_search_preview";
|
|
16
|
+
const reasoning = modelConfig.reasoning || reasoningEffort || reasoningMode
|
|
17
|
+
? {
|
|
18
|
+
...(modelConfig.reasoning ?? {}),
|
|
19
|
+
...(reasoningEffort ? { effort: reasoningEffort } : {}),
|
|
20
|
+
...(reasoningMode ? { mode: reasoningMode } : {}),
|
|
21
|
+
}
|
|
22
|
+
: undefined;
|
|
16
23
|
return {
|
|
17
24
|
model: modelConfig.apiModel ?? modelConfig.model,
|
|
18
25
|
previous_response_id: previousResponseId ? previousResponseId : undefined,
|
|
@@ -29,7 +36,7 @@ export function buildRequestBody({ modelConfig, systemPrompt, userPrompt, search
|
|
|
29
36
|
},
|
|
30
37
|
],
|
|
31
38
|
tools: searchEnabled ? [{ type: searchToolType }] : undefined,
|
|
32
|
-
reasoning
|
|
39
|
+
reasoning,
|
|
33
40
|
max_output_tokens: maxOutputTokens,
|
|
34
41
|
background: background ? true : undefined,
|
|
35
42
|
store: storeResponse ? true : undefined,
|
package/dist/src/oracle/run.js
CHANGED
|
@@ -33,6 +33,9 @@ const dim = (text) => (isStdoutTty ? kleur.dim(text) : text);
|
|
|
33
33
|
// Default timeout for non-pro API runs (fast models) — give them up to 120s.
|
|
34
34
|
const DEFAULT_TIMEOUT_NON_PRO_MS = 120_000;
|
|
35
35
|
const DEFAULT_TIMEOUT_PRO_MS = 60 * 60 * 1000;
|
|
36
|
+
const GPT_5_6_API_MODELS = new Set(["gpt-5.6", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"]);
|
|
37
|
+
const REASONING_EFFORTS = new Set(["none", "low", "medium", "high", "xhigh", "max"]);
|
|
38
|
+
const REASONING_MODES = new Set(["standard", "pro"]);
|
|
36
39
|
const defaultWait = (ms) => new Promise((resolve) => {
|
|
37
40
|
setTimeout(resolve, ms);
|
|
38
41
|
});
|
|
@@ -69,6 +72,33 @@ function runtimeKeySource({ route, providerMode, optionsApiKey, }) {
|
|
|
69
72
|
return "XAI_API_KEY";
|
|
70
73
|
return optionsApiKey ? "apiKey option" : route.keySource;
|
|
71
74
|
}
|
|
75
|
+
function validateReasoningOptions(options, route) {
|
|
76
|
+
const { reasoningEffort, reasoningMode } = options;
|
|
77
|
+
if (!reasoningEffort && !reasoningMode)
|
|
78
|
+
return;
|
|
79
|
+
if (reasoningEffort && !REASONING_EFFORTS.has(reasoningEffort)) {
|
|
80
|
+
throw new PromptValidationError(`Invalid reasoning effort "${reasoningEffort}". Expected none, low, medium, high, xhigh, or max.`, { model: options.model, reasoningEffort });
|
|
81
|
+
}
|
|
82
|
+
if (reasoningMode && !REASONING_MODES.has(reasoningMode)) {
|
|
83
|
+
throw new PromptValidationError(`Invalid reasoning mode "${reasoningMode}". Expected standard or pro.`, { model: options.model, reasoningMode });
|
|
84
|
+
}
|
|
85
|
+
if (!GPT_5_6_API_MODELS.has(options.model)) {
|
|
86
|
+
const option = reasoningMode
|
|
87
|
+
? `Reasoning mode "${reasoningMode}"`
|
|
88
|
+
: `Reasoning effort "${reasoningEffort}"`;
|
|
89
|
+
const guidance = reasoningMode
|
|
90
|
+
? `Use --model gpt-5.6-sol --reasoning-mode ${reasoningMode}.`
|
|
91
|
+
: `Use --model gpt-5.6-sol --reasoning-effort ${reasoningEffort}.`;
|
|
92
|
+
throw new PromptValidationError(`${option} is available only for GPT-5.6 API models. ${guidance}`, { model: options.model, reasoningEffort, reasoningMode });
|
|
93
|
+
}
|
|
94
|
+
if (reasoningMode &&
|
|
95
|
+
!route.isAzureOpenAI &&
|
|
96
|
+
(route.openRouterFallback ||
|
|
97
|
+
isOpenRouterBaseUrl(route.baseUrl) ||
|
|
98
|
+
isCustomBaseUrl(route.baseUrl))) {
|
|
99
|
+
throw new PromptValidationError("--reasoning-mode requires the OpenAI or Azure OpenAI Responses API; OpenRouter and custom --base-url routes use the Chat Completions adapter.", { model: options.model, reasoningMode });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
72
102
|
export async function runOracle(options, deps = {}) {
|
|
73
103
|
const { apiKey: optionsApiKey = options.apiKey, cwd = process.cwd(), fs: fsModule = createFsAdapter(fs), log = console.log, write: sinkWrite = (_text) => true, allowStdout = true, stdoutWrite: stdoutWriteDep, now = () => performance.now(), clientFactory = createDefaultClientFactory(), client, wait = defaultWait, } = deps;
|
|
74
104
|
const stdoutWrite = allowStdout
|
|
@@ -100,6 +130,7 @@ export async function runOracle(options, deps = {}) {
|
|
|
100
130
|
const { isAzureOpenAI, azureDeploymentName } = route;
|
|
101
131
|
const baseUrl = route.baseUrl;
|
|
102
132
|
const openRouterFallback = route.openRouterFallback;
|
|
133
|
+
validateReasoningOptions(options, route);
|
|
103
134
|
const logVerbose = (message) => {
|
|
104
135
|
if (options.verbose) {
|
|
105
136
|
log(dim(`[verbose] ${message}`));
|
|
@@ -125,7 +156,7 @@ export async function runOracle(options, deps = {}) {
|
|
|
125
156
|
const minPromptLength = Number.parseInt(process.env.ORACLE_MIN_PROMPT_CHARS ?? "10", 10);
|
|
126
157
|
const promptLength = options.prompt?.trim().length ?? 0;
|
|
127
158
|
// Enforce the short-prompt guardrail on pro-tier models because they're costly; cheaper models can run short prompts without blocking.
|
|
128
|
-
const isProTierModel = isProModel(options.model);
|
|
159
|
+
const isProTierModel = isProModel(options.model) || options.reasoningMode === "pro";
|
|
129
160
|
if (isProTierModel && !Number.isNaN(minPromptLength) && promptLength < minPromptLength) {
|
|
130
161
|
throw new PromptValidationError(`Prompt is too short (<${minPromptLength} chars). This was likely accidental; please provide more detail.`, { minPromptLength, promptLength });
|
|
131
162
|
}
|
|
@@ -205,6 +236,8 @@ export async function runOracle(options, deps = {}) {
|
|
|
205
236
|
}
|
|
206
237
|
const requestBody = buildRequestBody({
|
|
207
238
|
modelConfig,
|
|
239
|
+
reasoningEffort: options.reasoningEffort,
|
|
240
|
+
reasoningMode: options.reasoningMode,
|
|
208
241
|
systemPrompt,
|
|
209
242
|
userPrompt: promptWithFiles,
|
|
210
243
|
searchEnabled,
|
|
@@ -249,6 +282,12 @@ export async function runOracle(options, deps = {}) {
|
|
|
249
282
|
if (options.background && !supportsBackground) {
|
|
250
283
|
log(dim("Background runs are not supported for this model; streaming in foreground instead."));
|
|
251
284
|
}
|
|
285
|
+
if (options.reasoningMode) {
|
|
286
|
+
log(dim(`Reasoning mode: ${options.reasoningMode}`));
|
|
287
|
+
}
|
|
288
|
+
if (options.reasoningEffort) {
|
|
289
|
+
log(dim(`Reasoning effort: ${options.reasoningEffort}`));
|
|
290
|
+
}
|
|
252
291
|
if (!options.suppressTips) {
|
|
253
292
|
if (pendingNoFilesTip) {
|
|
254
293
|
log(dim(pendingNoFilesTip));
|
|
@@ -547,8 +586,9 @@ export async function runOracle(options, deps = {}) {
|
|
|
547
586
|
},
|
|
548
587
|
})?.totalUsd
|
|
549
588
|
: undefined;
|
|
550
|
-
const effortLabel = modelConfig.reasoning?.effort;
|
|
551
|
-
const
|
|
589
|
+
const effortLabel = options.reasoningEffort ?? modelConfig.reasoning?.effort;
|
|
590
|
+
const reasoningLabel = [options.reasoningMode, effortLabel].filter(Boolean).join("/");
|
|
591
|
+
const modelLabel = reasoningLabel ? `${modelConfig.model}[${reasoningLabel}]` : modelConfig.model;
|
|
552
592
|
const sessionIdContainsModel = typeof options.sessionId === "string" &&
|
|
553
593
|
options.sessionId.toLowerCase().includes(modelConfig.model.toLowerCase());
|
|
554
594
|
const tokensDisplay = [inputTokens, outputTokens, reasoningTokens, totalTokens]
|