@bubblebrain-ai/bubble 0.0.13 → 0.0.15
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/dist/agent/execution-governor.js +1 -1
- package/dist/agent/tool-intent.js +1 -0
- package/dist/agent.d.ts +2 -0
- package/dist/agent.js +589 -316
- package/dist/approval/controller.d.ts +1 -0
- package/dist/approval/controller.js +20 -3
- package/dist/approval/tool-helper.js +2 -0
- package/dist/approval/types.d.ts +14 -1
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +12 -0
- package/dist/context/compact.js +9 -3
- package/dist/context/projector.js +27 -12
- package/dist/debug-trace.d.ts +27 -0
- package/dist/debug-trace.js +385 -0
- package/dist/feishu/agent-host/approval-card.js +9 -0
- package/dist/feishu/serve.js +7 -1
- package/dist/main.js +41 -0
- package/dist/model-catalog.js +1 -0
- package/dist/orchestrator/default-hooks.js +19 -8
- package/dist/orchestrator/hooks.d.ts +1 -0
- package/dist/prompt/environment.js +2 -0
- package/dist/prompt/reminders.d.ts +5 -6
- package/dist/prompt/reminders.js +8 -9
- package/dist/prompt/runtime.js +2 -2
- package/dist/provider-openai-codex.d.ts +7 -0
- package/dist/provider-openai-codex.js +265 -124
- package/dist/provider-registry.d.ts +2 -0
- package/dist/provider-registry.js +58 -9
- package/dist/provider.d.ts +3 -0
- package/dist/provider.js +5 -1
- package/dist/session-log.js +13 -1
- package/dist/slash-commands/commands.js +12 -0
- package/dist/slash-commands/types.d.ts +2 -0
- package/dist/stats/usage.d.ts +52 -0
- package/dist/stats/usage.js +414 -0
- package/dist/tools/apply-patch.d.ts +9 -0
- package/dist/tools/apply-patch.js +330 -0
- package/dist/tools/bash.js +205 -44
- package/dist/tools/edit-apply.d.ts +5 -2
- package/dist/tools/edit-apply.js +221 -31
- package/dist/tools/edit.js +12 -3
- package/dist/tools/file-mutation-queue.d.ts +1 -0
- package/dist/tools/file-mutation-queue.js +12 -1
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +7 -1
- package/dist/tools/patch-apply.d.ts +41 -0
- package/dist/tools/patch-apply.js +312 -0
- package/dist/tools/server-manager.d.ts +36 -0
- package/dist/tools/server-manager.js +234 -0
- package/dist/tools/server.d.ts +6 -0
- package/dist/tools/server.js +245 -0
- package/dist/tools/write.d.ts +3 -6
- package/dist/tools/write.js +26 -46
- package/dist/tui/display-history.d.ts +1 -0
- package/dist/tui/display-history.js +5 -4
- package/dist/tui/edit-diff.js +6 -1
- package/dist/tui/model-picker-data.d.ts +10 -0
- package/dist/tui/model-picker-data.js +32 -0
- package/dist/tui/run.d.ts +2 -0
- package/dist/tui/run.js +717 -122
- package/dist/tui/tool-renderers/fallback.js +1 -1
- package/dist/tui/tool-renderers/write-preview.js +2 -0
- package/dist/tui/trace-groups.js +10 -3
- package/dist/tui-ink/app.js +1 -4
- package/dist/tui-ink/approval/approval-dialog.js +7 -1
- package/dist/tui-ink/display-history.d.ts +1 -0
- package/dist/tui-ink/display-history.js +5 -4
- package/dist/tui-ink/message-list.js +14 -8
- package/dist/tui-ink/trace-groups.js +1 -1
- package/dist/tui-opentui/app.js +2 -0
- package/dist/tui-opentui/approval/approval-dialog.js +7 -1
- package/dist/tui-opentui/display-history.d.ts +1 -0
- package/dist/tui-opentui/display-history.js +5 -4
- package/dist/tui-opentui/edit-diff.js +6 -1
- package/dist/tui-opentui/message-list.js +6 -3
- package/dist/tui-opentui/trace-groups.js +10 -3
- package/dist/types.d.ts +12 -2
- package/dist/update/index.d.ts +46 -0
- package/dist/update/index.js +240 -0
- package/package.json +1 -1
package/dist/tui/run.js
CHANGED
|
@@ -9,10 +9,12 @@ import { homedir } from "node:os";
|
|
|
9
9
|
import { AgentAbortError } from "../agent.js";
|
|
10
10
|
import { AgentRunInputQueue } from "../agent/input-controller.js";
|
|
11
11
|
import { debugReasoningStream, summarizeDebugText } from "../reasoning-debug.js";
|
|
12
|
+
import { summarizeAgentEventForTrace, summarizeTraceError, summarizeTraceValue, traceEvent, } from "../debug-trace.js";
|
|
12
13
|
import { BUILTIN_PROVIDERS, decodeModel, displayModel, isUserVisibleProvider } from "../provider-registry.js";
|
|
13
|
-
import { listBuiltinModels } from "../model-catalog.js";
|
|
14
14
|
import { calculateUsageCost } from "../model-pricing.js";
|
|
15
15
|
import { getAvailableThinkingLevels } from "../provider-transform.js";
|
|
16
|
+
import { getCurrentVersion } from "../update/index.js";
|
|
17
|
+
import { collectUsageStatsBundle, formatStatsPanelBody } from "../stats/usage.js";
|
|
16
18
|
import { parseSkillInvocation } from "../skills/invocation.js";
|
|
17
19
|
import { registry as slashRegistry } from "../slash-commands/index.js";
|
|
18
20
|
import { sourceRank } from "../slash-commands/unified.js";
|
|
@@ -24,6 +26,7 @@ import { markdownInlineSegments } from "./markdown-inline.js";
|
|
|
24
26
|
import { hashString } from "./render-signature.js";
|
|
25
27
|
import { findToolRenderer } from "./tool-renderers/registry.js";
|
|
26
28
|
import { writeToolKey } from "./tool-renderers/write.js";
|
|
29
|
+
import { discoverModelProviderGroups, getVisibleModelProviders, localModelsForProvider, } from "./model-picker-data.js";
|
|
27
30
|
import { formatWritePreview, isWritePreviewTool } from "./tool-renderers/write-preview.js";
|
|
28
31
|
import { extractStreamingArgsHint } from "./streaming-tool-args.js";
|
|
29
32
|
import { getNextPermissionMode, PERMISSION_MODE_INFO } from "../permission/mode.js";
|
|
@@ -179,7 +182,7 @@ const PROMPT_SCANNER_IDLE_FRAMES = [" "];
|
|
|
179
182
|
const PROMPT_SCANNER_INTERVAL_MS = 80;
|
|
180
183
|
const SESSION_SIDEBAR_WIDTH = 42;
|
|
181
184
|
const SESSION_SIDEBAR_AUTO_WIDTH = 120;
|
|
182
|
-
const PROVIDER_DIALOG_ROWS =
|
|
185
|
+
const PROVIDER_DIALOG_ROWS = 13;
|
|
183
186
|
const QUESTION_MAX_TABS = 4;
|
|
184
187
|
const QUESTION_MAX_OPTIONS = 10;
|
|
185
188
|
const QUESTION_MAX_CONFIRM_ROWS = 3;
|
|
@@ -396,6 +399,7 @@ function OpenTuiApp(props) {
|
|
|
396
399
|
redrawProviderDialog();
|
|
397
400
|
redrawApprovalPanel();
|
|
398
401
|
redrawQuestionPanel();
|
|
402
|
+
redrawStatsPanel();
|
|
399
403
|
redrawFeishuSetupPanel();
|
|
400
404
|
setSidebarTick((tick) => tick + 1);
|
|
401
405
|
renderer.requestRender();
|
|
@@ -483,6 +487,7 @@ function OpenTuiApp(props) {
|
|
|
483
487
|
const [pendingQuestion, setPendingQuestion] = createSignal();
|
|
484
488
|
const [pendingFeedback, setPendingFeedback] = createSignal();
|
|
485
489
|
const [pendingFeishuSetup, setPendingFeishuSetup] = createSignal();
|
|
490
|
+
let statsPanel;
|
|
486
491
|
const questionSyncTimers = new Set();
|
|
487
492
|
let feishuSetupAbortController;
|
|
488
493
|
let pendingApprovalRef;
|
|
@@ -490,6 +495,8 @@ function OpenTuiApp(props) {
|
|
|
490
495
|
const [approvalOptionIdx, setApprovalOptionIdx] = createSignal(0);
|
|
491
496
|
let picker;
|
|
492
497
|
let providerDialog;
|
|
498
|
+
let providerDialogModelItems;
|
|
499
|
+
let providerDialogModelRefreshId = 0;
|
|
493
500
|
let previousPickerForKey;
|
|
494
501
|
let homePromptRef;
|
|
495
502
|
let sessionPromptRef;
|
|
@@ -546,6 +553,17 @@ function OpenTuiApp(props) {
|
|
|
546
553
|
let feedbackPreviewShell;
|
|
547
554
|
let feedbackPreviewText;
|
|
548
555
|
let feedbackFooterText;
|
|
556
|
+
let statsRoot;
|
|
557
|
+
let statsPanelBox;
|
|
558
|
+
let statsTitle;
|
|
559
|
+
let statsEsc;
|
|
560
|
+
let statsTab7Box;
|
|
561
|
+
let statsTab30Box;
|
|
562
|
+
let statsTab7Text;
|
|
563
|
+
let statsTab30Text;
|
|
564
|
+
let statsBodyScroll;
|
|
565
|
+
let statsBodyText;
|
|
566
|
+
let statsFooterText;
|
|
549
567
|
let feishuSetupRoot;
|
|
550
568
|
let feishuSetupPanel;
|
|
551
569
|
let feishuSetupTitle;
|
|
@@ -713,6 +731,12 @@ function OpenTuiApp(props) {
|
|
|
713
731
|
feedbackRoot?.focus();
|
|
714
732
|
}, 0);
|
|
715
733
|
}
|
|
734
|
+
function focusStatsPanel() {
|
|
735
|
+
setTimeout(() => {
|
|
736
|
+
if (statsPanel)
|
|
737
|
+
statsRoot?.focus();
|
|
738
|
+
}, 0);
|
|
739
|
+
}
|
|
716
740
|
function focusFeishuSetupPanel() {
|
|
717
741
|
setTimeout(() => {
|
|
718
742
|
const state = pendingFeishuSetup();
|
|
@@ -852,7 +876,7 @@ function OpenTuiApp(props) {
|
|
|
852
876
|
return false;
|
|
853
877
|
return !!event.shift;
|
|
854
878
|
};
|
|
855
|
-
const canInsertPromptNewline = () => !pendingApproval() && !pendingPlan() && !pendingQuestion() && !pendingFeedback() && !pendingFeishuSetup();
|
|
879
|
+
const canInsertPromptNewline = () => !pendingApproval() && !pendingPlan() && !pendingQuestion() && !pendingFeedback() && !statsPanel && !pendingFeishuSetup();
|
|
856
880
|
const sidebarFits = () => dimensions().width > SESSION_SIDEBAR_WIDTH + 40;
|
|
857
881
|
const sidebarVisible = () => {
|
|
858
882
|
if (!sessionActive())
|
|
@@ -1081,7 +1105,7 @@ function OpenTuiApp(props) {
|
|
|
1081
1105
|
if (!safeSetText(ref, promptModeBadge()))
|
|
1082
1106
|
promptModeLabels.delete(ref);
|
|
1083
1107
|
};
|
|
1084
|
-
const promptModelTitle = () =>
|
|
1108
|
+
const promptModelTitle = () => displayModelWithThinking(props.agent.model, props.agent.thinking) || "no model";
|
|
1085
1109
|
const syncModelChrome = () => {
|
|
1086
1110
|
if (uiDisposed)
|
|
1087
1111
|
return;
|
|
@@ -1193,6 +1217,7 @@ function OpenTuiApp(props) {
|
|
|
1193
1217
|
switch (request.type) {
|
|
1194
1218
|
case "bash": return "Bash";
|
|
1195
1219
|
case "edit": return "Edit";
|
|
1220
|
+
case "patch": return "Patch";
|
|
1196
1221
|
case "write": return "Write";
|
|
1197
1222
|
case "lsp": return "Lsp";
|
|
1198
1223
|
}
|
|
@@ -1798,6 +1823,71 @@ function OpenTuiApp(props) {
|
|
|
1798
1823
|
}
|
|
1799
1824
|
return false;
|
|
1800
1825
|
}
|
|
1826
|
+
function openStatsPanel() {
|
|
1827
|
+
picker = undefined;
|
|
1828
|
+
providerDialog = undefined;
|
|
1829
|
+
redrawProviderDialog();
|
|
1830
|
+
statsPanel = {
|
|
1831
|
+
range: "30d",
|
|
1832
|
+
bundle: collectUsageStatsBundle(),
|
|
1833
|
+
};
|
|
1834
|
+
activePrompt()?.clear();
|
|
1835
|
+
activePrompt()?.blur();
|
|
1836
|
+
promptText = "";
|
|
1837
|
+
syncStatsUI(true);
|
|
1838
|
+
}
|
|
1839
|
+
function closeStatsPanel() {
|
|
1840
|
+
statsPanel = undefined;
|
|
1841
|
+
syncStatsUI(false);
|
|
1842
|
+
restorePromptAfterModal();
|
|
1843
|
+
if (queuedInputCount() > 0)
|
|
1844
|
+
scheduleQueuedInputDrain();
|
|
1845
|
+
}
|
|
1846
|
+
function syncStatsUI(focus = false) {
|
|
1847
|
+
redrawStatsPanel();
|
|
1848
|
+
syncPromptSurfaces();
|
|
1849
|
+
redrawDock();
|
|
1850
|
+
rootBox?.requestRender();
|
|
1851
|
+
scrollbox?.requestRender();
|
|
1852
|
+
if (focus || statsPanel)
|
|
1853
|
+
focusStatsPanel();
|
|
1854
|
+
}
|
|
1855
|
+
function setStatsRange(range) {
|
|
1856
|
+
if (!statsPanel || statsPanel.range === range)
|
|
1857
|
+
return;
|
|
1858
|
+
statsPanel = { ...statsPanel, range };
|
|
1859
|
+
redrawStatsPanel();
|
|
1860
|
+
}
|
|
1861
|
+
function handleStatsKey(event) {
|
|
1862
|
+
if (!statsPanel)
|
|
1863
|
+
return false;
|
|
1864
|
+
const name = keyNameFromEvent(event);
|
|
1865
|
+
if (name === "escape") {
|
|
1866
|
+
closeStatsPanel();
|
|
1867
|
+
event.preventDefault?.();
|
|
1868
|
+
event.stopPropagation?.();
|
|
1869
|
+
return true;
|
|
1870
|
+
}
|
|
1871
|
+
if (name === "left" || name === "h") {
|
|
1872
|
+
setStatsRange("7d");
|
|
1873
|
+
event.preventDefault?.();
|
|
1874
|
+
event.stopPropagation?.();
|
|
1875
|
+
return true;
|
|
1876
|
+
}
|
|
1877
|
+
if (name === "right" || name === "l") {
|
|
1878
|
+
setStatsRange("30d");
|
|
1879
|
+
event.preventDefault?.();
|
|
1880
|
+
event.stopPropagation?.();
|
|
1881
|
+
return true;
|
|
1882
|
+
}
|
|
1883
|
+
if (name === "tab") {
|
|
1884
|
+
setStatsRange(statsPanel.range === "30d" ? "7d" : "30d");
|
|
1885
|
+
event.preventDefault?.();
|
|
1886
|
+
event.stopPropagation?.();
|
|
1887
|
+
return true;
|
|
1888
|
+
}
|
|
1889
|
+
return true;
|
|
1890
|
+
}
|
|
1801
1891
|
function openFeishuSetup() {
|
|
1802
1892
|
picker = undefined;
|
|
1803
1893
|
providerDialog = undefined;
|
|
@@ -2048,6 +2138,8 @@ function OpenTuiApp(props) {
|
|
|
2048
2138
|
return "question";
|
|
2049
2139
|
if (pendingFeedback())
|
|
2050
2140
|
return "feedback";
|
|
2141
|
+
if (statsPanel)
|
|
2142
|
+
return "stats";
|
|
2051
2143
|
if (providerDialog)
|
|
2052
2144
|
return "provider";
|
|
2053
2145
|
if (pendingFeishuSetup())
|
|
@@ -2067,6 +2159,8 @@ function OpenTuiApp(props) {
|
|
|
2067
2159
|
return handleQuestionKey(event);
|
|
2068
2160
|
case "feedback":
|
|
2069
2161
|
return handleFeedbackKey(event);
|
|
2162
|
+
case "stats":
|
|
2163
|
+
return handleStatsKey(event);
|
|
2070
2164
|
case "provider":
|
|
2071
2165
|
return handleProviderDialogKey(event);
|
|
2072
2166
|
case "feishu":
|
|
@@ -2084,6 +2178,8 @@ function OpenTuiApp(props) {
|
|
|
2084
2178
|
}
|
|
2085
2179
|
if (owner === "feedback")
|
|
2086
2180
|
return pendingFeedback()?.stage !== "edit";
|
|
2181
|
+
if (owner === "stats")
|
|
2182
|
+
return true;
|
|
2087
2183
|
if (owner === "feishu")
|
|
2088
2184
|
return pendingFeishuSetup()?.kind !== "binding";
|
|
2089
2185
|
return false;
|
|
@@ -2223,14 +2319,14 @@ function OpenTuiApp(props) {
|
|
|
2223
2319
|
return currentTranscriptMessages(extra).some((message) => hasRenderableMessage(message, effectiveShowThinking()));
|
|
2224
2320
|
}
|
|
2225
2321
|
function isHomeSurfaceActive(extra) {
|
|
2226
|
-
return !hasTranscriptMessages(extra) && !pendingPlan() && !pendingQuestion() && !pendingFeedback() && !pendingFeishuSetup();
|
|
2322
|
+
return !hasTranscriptMessages(extra) && !pendingPlan() && !pendingQuestion() && !pendingFeedback() && !statsPanel && !pendingFeishuSetup();
|
|
2227
2323
|
}
|
|
2228
2324
|
function syncPromptSurfaces(focus = false) {
|
|
2229
2325
|
const homeActive = isHomeSurfaceActive(streamingDisplay);
|
|
2230
2326
|
const nextSessionActive = !homeActive;
|
|
2231
2327
|
const surfaceChanged = sessionActive() !== nextSessionActive;
|
|
2232
2328
|
setSessionActive(nextSessionActive);
|
|
2233
|
-
const modalComposerHidden = !!pendingQuestion() || !!pendingFeedback() || !!pendingFeishuSetup();
|
|
2329
|
+
const modalComposerHidden = !!pendingQuestion() || !!pendingFeedback() || !!statsPanel || !!pendingFeishuSetup();
|
|
2234
2330
|
if (homeSurfaceShell)
|
|
2235
2331
|
homeSurfaceShell.visible = homeActive;
|
|
2236
2332
|
if (homeComposerShell)
|
|
@@ -2333,6 +2429,18 @@ function OpenTuiApp(props) {
|
|
|
2333
2429
|
redrawTranscriptWithQueuedDisplays();
|
|
2334
2430
|
return changed;
|
|
2335
2431
|
}
|
|
2432
|
+
function removeQueuedUserDisplay(displayId) {
|
|
2433
|
+
if (!displayId)
|
|
2434
|
+
return false;
|
|
2435
|
+
const beforeDisplayCount = displayMessages.length;
|
|
2436
|
+
const beforeQueuedCount = queuedDisplayMessages.length;
|
|
2437
|
+
displayMessages = displayMessages.filter((message) => message.clientId !== displayId);
|
|
2438
|
+
queuedDisplayMessages = queuedDisplayMessages.filter((message) => message.clientId !== displayId);
|
|
2439
|
+
const changed = displayMessages.length !== beforeDisplayCount || queuedDisplayMessages.length !== beforeQueuedCount;
|
|
2440
|
+
if (changed)
|
|
2441
|
+
redrawTranscriptWithQueuedDisplays();
|
|
2442
|
+
return changed;
|
|
2443
|
+
}
|
|
2336
2444
|
function promoteQueuedUserDisplay(displayId, fallbackContent) {
|
|
2337
2445
|
if (!displayId)
|
|
2338
2446
|
return false;
|
|
@@ -2425,6 +2533,7 @@ function OpenTuiApp(props) {
|
|
|
2425
2533
|
|| pendingPlan()
|
|
2426
2534
|
|| pendingQuestion()
|
|
2427
2535
|
|| pendingFeedback()
|
|
2536
|
+
|| statsPanel
|
|
2428
2537
|
|| providerDialog
|
|
2429
2538
|
|| picker) {
|
|
2430
2539
|
return;
|
|
@@ -2522,6 +2631,10 @@ function OpenTuiApp(props) {
|
|
|
2522
2631
|
function cancelActiveAgentRun() {
|
|
2523
2632
|
if (!activeRun || activeRun.abortController.signal.aborted)
|
|
2524
2633
|
return false;
|
|
2634
|
+
traceEvent("tui_running_cancel", {
|
|
2635
|
+
runId: activeRun.id,
|
|
2636
|
+
pendingQueuedInputs: queuedInputCount(),
|
|
2637
|
+
}, { surface: "tui" });
|
|
2525
2638
|
clearRunningCancelHint();
|
|
2526
2639
|
activeRun.abortController.abort(new AgentAbortError("Agent run cancelled by user."));
|
|
2527
2640
|
setNotice("Agent run cancelled");
|
|
@@ -2556,6 +2669,7 @@ function OpenTuiApp(props) {
|
|
|
2556
2669
|
if (!activeRun || activeRun.abortController.signal.aborted)
|
|
2557
2670
|
return false;
|
|
2558
2671
|
const shouldCancel = armRunningCancelHint(activeRun);
|
|
2672
|
+
traceKeyRoute(event ? "key" : "raw", name, !shouldCancel ? "armed_cancel" : "confirm_cancel");
|
|
2559
2673
|
if (!shouldCancel) {
|
|
2560
2674
|
if (event)
|
|
2561
2675
|
preventGlobalKey(event);
|
|
@@ -2573,33 +2687,72 @@ function OpenTuiApp(props) {
|
|
|
2573
2687
|
if (!isRunning() || activeModalKeyOwner())
|
|
2574
2688
|
return false;
|
|
2575
2689
|
queuePromptFromComposer({ notice: "Queued next message" });
|
|
2690
|
+
traceKeyRoute(event ? "key" : "raw", name, "queued_next_message");
|
|
2576
2691
|
if (event)
|
|
2577
2692
|
preventGlobalKey(event);
|
|
2578
2693
|
return true;
|
|
2579
2694
|
}
|
|
2695
|
+
function traceKeyRoute(source, name, result) {
|
|
2696
|
+
const shouldTrace = result !== "unhandled"
|
|
2697
|
+
|| name === "escape"
|
|
2698
|
+
|| name === "enter"
|
|
2699
|
+
|| name === "tab"
|
|
2700
|
+
|| name === "up"
|
|
2701
|
+
|| name === "down"
|
|
2702
|
+
|| name === "left"
|
|
2703
|
+
|| name === "right"
|
|
2704
|
+
|| name === "ctrl-c"
|
|
2705
|
+
|| !!activeModalKeyOwner()
|
|
2706
|
+
|| isRunning();
|
|
2707
|
+
if (!shouldTrace)
|
|
2708
|
+
return;
|
|
2709
|
+
traceEvent("tui_key_route", {
|
|
2710
|
+
source,
|
|
2711
|
+
key: name,
|
|
2712
|
+
result,
|
|
2713
|
+
modalOwner: activeModalKeyOwner(),
|
|
2714
|
+
running: isRunning(),
|
|
2715
|
+
activeRunId: activeRun?.id,
|
|
2716
|
+
pendingApproval: !!pendingApproval(),
|
|
2717
|
+
pendingPlan: !!pendingPlan(),
|
|
2718
|
+
pendingQuestion: !!pendingQuestion(),
|
|
2719
|
+
providerDialog: !!providerDialog,
|
|
2720
|
+
picker: !!picker,
|
|
2721
|
+
}, { surface: "tui" });
|
|
2722
|
+
}
|
|
2580
2723
|
function routeGlobalRawSequence(sequence) {
|
|
2581
2724
|
if (isCtrlCSequence(sequence)) {
|
|
2582
2725
|
void requestExit({ direct: true });
|
|
2726
|
+
traceKeyRoute("raw", "ctrl-c", "exit");
|
|
2583
2727
|
return true;
|
|
2584
2728
|
}
|
|
2585
2729
|
const name = keyNameFromSequence(sequence);
|
|
2586
2730
|
const modalName = modalKeyNameFromSequence(sequence);
|
|
2587
|
-
if (routeModalRawSequence(sequence))
|
|
2731
|
+
if (routeModalRawSequence(sequence)) {
|
|
2732
|
+
traceKeyRoute("raw", modalName || name, "modal");
|
|
2588
2733
|
return true;
|
|
2734
|
+
}
|
|
2589
2735
|
if (routeRunningCancel(name))
|
|
2590
2736
|
return true;
|
|
2591
2737
|
if (routeRunningQueue(modalName))
|
|
2592
2738
|
return true;
|
|
2593
|
-
if (cycleModeFromRawSequence(sequence))
|
|
2739
|
+
if (cycleModeFromRawSequence(sequence)) {
|
|
2740
|
+
traceKeyRoute("raw", name, "mode_cycle");
|
|
2594
2741
|
return true;
|
|
2742
|
+
}
|
|
2743
|
+
traceKeyRoute("raw", name, "unhandled");
|
|
2595
2744
|
return false;
|
|
2596
2745
|
}
|
|
2597
2746
|
function routeGlobalKeyEvent(event) {
|
|
2598
|
-
if (routeCtrlCExit(event))
|
|
2747
|
+
if (routeCtrlCExit(event)) {
|
|
2748
|
+
traceKeyRoute("key", "ctrl-c", "exit");
|
|
2599
2749
|
return true;
|
|
2750
|
+
}
|
|
2600
2751
|
const name = keyNameFromEvent(event);
|
|
2601
|
-
if (routeModalKey(event))
|
|
2752
|
+
if (routeModalKey(event)) {
|
|
2753
|
+
traceKeyRoute("key", name, "modal");
|
|
2602
2754
|
return true;
|
|
2755
|
+
}
|
|
2603
2756
|
if (routeRunningCancel(name, event))
|
|
2604
2757
|
return true;
|
|
2605
2758
|
if (routeRunningQueue(name, event))
|
|
@@ -2609,25 +2762,32 @@ function OpenTuiApp(props) {
|
|
|
2609
2762
|
if (event.ctrl && event.shift && name === "m") {
|
|
2610
2763
|
openMcpReconnectPicker();
|
|
2611
2764
|
event.preventDefault?.();
|
|
2765
|
+
traceKeyRoute("key", name, "mcp_picker");
|
|
2612
2766
|
return true;
|
|
2613
2767
|
}
|
|
2614
2768
|
if (event.ctrl && name === "t" && !picker) {
|
|
2615
2769
|
toggleThinkingVisibility();
|
|
2616
2770
|
event.preventDefault?.();
|
|
2771
|
+
traceKeyRoute("key", name, "toggle_thinking");
|
|
2617
2772
|
return true;
|
|
2618
2773
|
}
|
|
2619
2774
|
if (event.ctrl && name === "o" && !picker) {
|
|
2620
2775
|
toggleVerboseTrace();
|
|
2621
2776
|
event.preventDefault?.();
|
|
2777
|
+
traceKeyRoute("key", name, "toggle_verbose_trace");
|
|
2622
2778
|
return true;
|
|
2623
2779
|
}
|
|
2624
|
-
if (cycleModeFromKey(event))
|
|
2780
|
+
if (cycleModeFromKey(event)) {
|
|
2781
|
+
traceKeyRoute("key", name, "mode_cycle");
|
|
2625
2782
|
return true;
|
|
2783
|
+
}
|
|
2626
2784
|
if (event.ctrl && name === "p" && !picker && !isRunning()) {
|
|
2627
2785
|
openCommandPalette();
|
|
2628
2786
|
event.preventDefault?.();
|
|
2787
|
+
traceKeyRoute("key", name, "command_palette");
|
|
2629
2788
|
return true;
|
|
2630
2789
|
}
|
|
2790
|
+
traceKeyRoute("key", name, "unhandled");
|
|
2631
2791
|
return false;
|
|
2632
2792
|
}
|
|
2633
2793
|
function transcriptOptions() {
|
|
@@ -2739,6 +2899,7 @@ function OpenTuiApp(props) {
|
|
|
2739
2899
|
sessionActive();
|
|
2740
2900
|
syncSidebarChrome();
|
|
2741
2901
|
redrawQuestionPanel();
|
|
2902
|
+
redrawStatsPanel();
|
|
2742
2903
|
redrawFeishuSetupPanel();
|
|
2743
2904
|
scrollbox?.requestRender();
|
|
2744
2905
|
scheduleTranscriptScrollAfterUpdate(shouldFollow);
|
|
@@ -2800,6 +2961,13 @@ function OpenTuiApp(props) {
|
|
|
2800
2961
|
dock?.requestRender();
|
|
2801
2962
|
}
|
|
2802
2963
|
function openProviderDialog(step = "providers", providerId) {
|
|
2964
|
+
if (step === "models") {
|
|
2965
|
+
providerDialogModelItems = undefined;
|
|
2966
|
+
}
|
|
2967
|
+
else {
|
|
2968
|
+
providerDialogModelRefreshId++;
|
|
2969
|
+
providerDialogModelItems = undefined;
|
|
2970
|
+
}
|
|
2803
2971
|
const items = providerDialogItemsFor(step, providerId);
|
|
2804
2972
|
picker = undefined;
|
|
2805
2973
|
providerDialog = {
|
|
@@ -2815,9 +2983,14 @@ function OpenTuiApp(props) {
|
|
|
2815
2983
|
redrawDock();
|
|
2816
2984
|
redrawProviderDialog();
|
|
2817
2985
|
setTimeout(() => providerDialogInput?.focus(), 0);
|
|
2986
|
+
if (step === "models") {
|
|
2987
|
+
void refreshProviderDialogModelItems(providerId, items);
|
|
2988
|
+
}
|
|
2818
2989
|
}
|
|
2819
2990
|
function closeProviderDialog() {
|
|
2820
2991
|
providerDialog = undefined;
|
|
2992
|
+
providerDialogModelRefreshId++;
|
|
2993
|
+
providerDialogModelItems = undefined;
|
|
2821
2994
|
providerDialogRoot && (providerDialogRoot.visible = false);
|
|
2822
2995
|
providerDialogPanel && (providerDialogPanel.visible = false);
|
|
2823
2996
|
providerDialogRoot?.requestRender();
|
|
@@ -2833,6 +3006,9 @@ function OpenTuiApp(props) {
|
|
|
2833
3006
|
if (step === "skills")
|
|
2834
3007
|
return buildSkillItems();
|
|
2835
3008
|
if (step === "models") {
|
|
3009
|
+
if (providerDialogModelItems?.key === modelPickerCacheKey(providerId)) {
|
|
3010
|
+
return providerDialogModelItems.items;
|
|
3011
|
+
}
|
|
2836
3012
|
const modelItems = buildPickerItems("model", providerId);
|
|
2837
3013
|
if (modelItems.length || providerId)
|
|
2838
3014
|
return modelItems;
|
|
@@ -2843,6 +3019,59 @@ function OpenTuiApp(props) {
|
|
|
2843
3019
|
}
|
|
2844
3020
|
return [];
|
|
2845
3021
|
}
|
|
3022
|
+
function modelPickerCacheKey(providerId) {
|
|
3023
|
+
return providerId || "__all__";
|
|
3024
|
+
}
|
|
3025
|
+
async function refreshProviderDialogModelItems(providerId, localItems) {
|
|
3026
|
+
const refreshId = ++providerDialogModelRefreshId;
|
|
3027
|
+
const cacheKey = modelPickerCacheKey(providerId);
|
|
3028
|
+
const localPreferredIndex = preferredPickerIndex("model", localItems);
|
|
3029
|
+
try {
|
|
3030
|
+
const remoteItems = await buildRemoteModelPickerItems(providerId);
|
|
3031
|
+
if (refreshId !== providerDialogModelRefreshId)
|
|
3032
|
+
return;
|
|
3033
|
+
if (remoteItems.length === 0)
|
|
3034
|
+
return;
|
|
3035
|
+
const state = providerDialog;
|
|
3036
|
+
if (!state || state.step !== "models" || modelPickerCacheKey(state.providerId) !== cacheKey)
|
|
3037
|
+
return;
|
|
3038
|
+
providerDialogModelItems = { key: cacheKey, items: remoteItems };
|
|
3039
|
+
const remotePreferredIndex = preferredPickerIndex("model", remoteItems);
|
|
3040
|
+
const nextIndex = state.index === localPreferredIndex
|
|
3041
|
+
? remotePreferredIndex
|
|
3042
|
+
: Math.min(state.index, Math.max(0, remoteItems.length - 1));
|
|
3043
|
+
providerDialog = { ...state, index: nextIndex };
|
|
3044
|
+
redrawProviderDialog();
|
|
3045
|
+
}
|
|
3046
|
+
catch {
|
|
3047
|
+
// Keep the already-rendered local catalog when remote model discovery fails.
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
function providerDialogMatchScore(item, query) {
|
|
3051
|
+
const label = (item.label || "").toLowerCase();
|
|
3052
|
+
const value = (item.value || "").toLowerCase();
|
|
3053
|
+
const haystack = [
|
|
3054
|
+
item.label,
|
|
3055
|
+
item.detail,
|
|
3056
|
+
item.value,
|
|
3057
|
+
item.category,
|
|
3058
|
+
item.footer,
|
|
3059
|
+
].filter(Boolean).join(" ").toLowerCase();
|
|
3060
|
+
if (label.startsWith(query))
|
|
3061
|
+
return 100;
|
|
3062
|
+
if (label.includes(query))
|
|
3063
|
+
return 80;
|
|
3064
|
+
if (value.includes(query))
|
|
3065
|
+
return 60;
|
|
3066
|
+
if (haystack.includes(query))
|
|
3067
|
+
return 40;
|
|
3068
|
+
// Fuzzy (subsequence) match is a last resort, and only against label+value
|
|
3069
|
+
// so long provider descriptions (e.g. "platform.moonshot.cn") don't produce
|
|
3070
|
+
// spurious hits like "gpt" matching "kimi-k2-thinking".
|
|
3071
|
+
if (fuzzyMatch(`${label} ${value}`, query))
|
|
3072
|
+
return 20;
|
|
3073
|
+
return 0;
|
|
3074
|
+
}
|
|
2846
3075
|
function providerDialogFilteredItems(state = providerDialog) {
|
|
2847
3076
|
if (!state || state.step === "key")
|
|
2848
3077
|
return [];
|
|
@@ -2850,16 +3079,12 @@ function OpenTuiApp(props) {
|
|
|
2850
3079
|
const query = state.query.trim().toLowerCase();
|
|
2851
3080
|
if (!query)
|
|
2852
3081
|
return items;
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
item.footer,
|
|
2860
|
-
].filter(Boolean).join(" ").toLowerCase();
|
|
2861
|
-
return haystack.includes(query) || fuzzyMatch(haystack, query);
|
|
2862
|
-
});
|
|
3082
|
+
const scored = items
|
|
3083
|
+
.map((item, order) => ({ item, order, score: providerDialogMatchScore(item, query) }))
|
|
3084
|
+
.filter((entry) => entry.score > 0);
|
|
3085
|
+
// Stable sort by score desc, preserving original catalog order within a tier.
|
|
3086
|
+
scored.sort((a, b) => b.score - a.score || a.order - b.order);
|
|
3087
|
+
return scored.map((entry) => entry.item);
|
|
2863
3088
|
}
|
|
2864
3089
|
function providerDialogVisibleRows(state = providerDialog) {
|
|
2865
3090
|
if (!state)
|
|
@@ -2900,7 +3125,7 @@ function OpenTuiApp(props) {
|
|
|
2900
3125
|
providerDialogRoot.requestRender();
|
|
2901
3126
|
return;
|
|
2902
3127
|
}
|
|
2903
|
-
const width = Math.max(
|
|
3128
|
+
const width = Math.max(56, Math.min(76, dimensions().width - 4));
|
|
2904
3129
|
const height = PROVIDER_DIALOG_ROWS + 7;
|
|
2905
3130
|
providerDialogRoot.visible = true;
|
|
2906
3131
|
providerDialogRoot.width = dimensions().width;
|
|
@@ -3434,6 +3659,127 @@ function OpenTuiApp(props) {
|
|
|
3434
3659
|
feedbackRoot.requestRender();
|
|
3435
3660
|
feedbackInput?.requestRender();
|
|
3436
3661
|
}
|
|
3662
|
+
function redrawStatsPanel() {
|
|
3663
|
+
if (!statsRoot)
|
|
3664
|
+
return;
|
|
3665
|
+
const state = statsPanel;
|
|
3666
|
+
if (!state) {
|
|
3667
|
+
statsRoot.visible = false;
|
|
3668
|
+
statsPanelBox && (statsPanelBox.visible = false);
|
|
3669
|
+
statsRoot.requestRender();
|
|
3670
|
+
return;
|
|
3671
|
+
}
|
|
3672
|
+
const terminalWidth = dimensions().width;
|
|
3673
|
+
const terminalHeight = dimensions().height;
|
|
3674
|
+
const width = Math.max(56, Math.min(84, terminalWidth - 4));
|
|
3675
|
+
const bodyWidth = Math.max(48, width - 8);
|
|
3676
|
+
const stats = state.bundle.ranges[state.range];
|
|
3677
|
+
const body = formatStatsPanelBody(stats, bodyWidth);
|
|
3678
|
+
const bodyLines = body.split("\n");
|
|
3679
|
+
const height = Math.min(Math.max(22, bodyLines.length + 7), Math.max(18, terminalHeight - 4));
|
|
3680
|
+
const bodyHeight = Math.max(8, height - 8);
|
|
3681
|
+
statsRoot.visible = true;
|
|
3682
|
+
statsRoot.width = terminalWidth;
|
|
3683
|
+
statsRoot.height = terminalHeight;
|
|
3684
|
+
statsRoot.left = 0;
|
|
3685
|
+
statsRoot.top = 0;
|
|
3686
|
+
statsRoot.backgroundColor = modalBackdropColor();
|
|
3687
|
+
if (statsPanelBox) {
|
|
3688
|
+
statsPanelBox.visible = true;
|
|
3689
|
+
statsPanelBox.width = width;
|
|
3690
|
+
statsPanelBox.height = height;
|
|
3691
|
+
statsPanelBox.left = Math.max(0, Math.floor((terminalWidth - width) / 2));
|
|
3692
|
+
statsPanelBox.top = Math.max(0, Math.floor((terminalHeight - height) / 3));
|
|
3693
|
+
statsPanelBox.backgroundColor = theme.backgroundPanel;
|
|
3694
|
+
statsPanelBox.borderColor = theme.backgroundPanel;
|
|
3695
|
+
}
|
|
3696
|
+
if (statsTitle)
|
|
3697
|
+
statsTitle.content = "Stats";
|
|
3698
|
+
if (statsEsc)
|
|
3699
|
+
statsEsc.content = "esc";
|
|
3700
|
+
syncStatsTab(statsTab7Box, statsTab7Text, state.range === "7d", "7 days");
|
|
3701
|
+
syncStatsTab(statsTab30Box, statsTab30Text, state.range === "30d", "30 days");
|
|
3702
|
+
if (statsBodyText) {
|
|
3703
|
+
statsBodyText.content = statsPanelBodyStyledText(stats, bodyWidth);
|
|
3704
|
+
statsBodyText.width = bodyWidth;
|
|
3705
|
+
}
|
|
3706
|
+
if (statsBodyScroll) {
|
|
3707
|
+
statsBodyScroll.width = bodyWidth;
|
|
3708
|
+
statsBodyScroll.height = bodyHeight;
|
|
3709
|
+
statsBodyScroll.requestRender();
|
|
3710
|
+
}
|
|
3711
|
+
if (statsFooterText) {
|
|
3712
|
+
statsFooterText.content = statsFooterHint(state.range);
|
|
3713
|
+
statsFooterText.width = bodyWidth;
|
|
3714
|
+
statsFooterText.bg = theme.backgroundPanel;
|
|
3715
|
+
}
|
|
3716
|
+
statsPanelBox?.requestRender();
|
|
3717
|
+
statsRoot.requestRender();
|
|
3718
|
+
}
|
|
3719
|
+
function statsFooterHint(range) {
|
|
3720
|
+
return `left/right:range|tab:toggle|esc:close|view:${range}`;
|
|
3721
|
+
}
|
|
3722
|
+
function statsPanelBodyStyledText(stats, width) {
|
|
3723
|
+
const chunks = [];
|
|
3724
|
+
const lines = formatStatsPanelBody(stats, width).split("\n");
|
|
3725
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
3726
|
+
appendStatsPanelLine(chunks, lines[index]);
|
|
3727
|
+
if (index < lines.length - 1)
|
|
3728
|
+
chunks.push(fg(theme.text)("\n"));
|
|
3729
|
+
}
|
|
3730
|
+
return new StyledText(chunks);
|
|
3731
|
+
}
|
|
3732
|
+
function appendStatsPanelLine(chunks, line) {
|
|
3733
|
+
if (isStatsHeatmapWeekdayLine(line)) {
|
|
3734
|
+
chunks.push(fg(theme.text)(line.slice(0, 5)));
|
|
3735
|
+
appendStatsHeatmapDots(chunks, line.slice(5));
|
|
3736
|
+
return;
|
|
3737
|
+
}
|
|
3738
|
+
if (line.trim() === "Less . o O @ More") {
|
|
3739
|
+
appendStatsHeatmapLegend(chunks, line.length - line.trimStart().length);
|
|
3740
|
+
return;
|
|
3741
|
+
}
|
|
3742
|
+
chunks.push(fg(theme.text)(line));
|
|
3743
|
+
}
|
|
3744
|
+
function isStatsHeatmapWeekdayLine(line) {
|
|
3745
|
+
return /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) /.test(line);
|
|
3746
|
+
}
|
|
3747
|
+
function appendStatsHeatmapDots(chunks, text) {
|
|
3748
|
+
const colors = statsHeatmapDotColors();
|
|
3749
|
+
const colorByLevel = {
|
|
3750
|
+
".": colors[0],
|
|
3751
|
+
o: colors[1],
|
|
3752
|
+
O: colors[2],
|
|
3753
|
+
"@": colors[3],
|
|
3754
|
+
};
|
|
3755
|
+
for (const char of text) {
|
|
3756
|
+
const color = colorByLevel[char];
|
|
3757
|
+
chunks.push(color ? fg(color)("•") : fg(theme.text)(char));
|
|
3758
|
+
}
|
|
3759
|
+
}
|
|
3760
|
+
function appendStatsHeatmapLegend(chunks, indent) {
|
|
3761
|
+
const colors = statsHeatmapDotColors();
|
|
3762
|
+
chunks.push(fg(theme.textMuted)(`${" ".repeat(indent)}Less `));
|
|
3763
|
+
colors.forEach((color, index) => {
|
|
3764
|
+
if (index > 0)
|
|
3765
|
+
chunks.push(fg(theme.textMuted)(" "));
|
|
3766
|
+
chunks.push(fg(color)("•"));
|
|
3767
|
+
});
|
|
3768
|
+
chunks.push(fg(theme.textMuted)(" More"));
|
|
3769
|
+
}
|
|
3770
|
+
function statsHeatmapDotColors() {
|
|
3771
|
+
return isLightTheme()
|
|
3772
|
+
? ["#D9B98E", "#BE7D37", "#A56218", theme.warning]
|
|
3773
|
+
: ["#6B471D", "#9D6728", "#D18830", theme.warning];
|
|
3774
|
+
}
|
|
3775
|
+
function syncStatsTab(box, text, active, label) {
|
|
3776
|
+
if (box)
|
|
3777
|
+
box.backgroundColor = active ? theme.primary : theme.backgroundElement;
|
|
3778
|
+
if (text) {
|
|
3779
|
+
text.content = label;
|
|
3780
|
+
text.fg = active ? contrastText(theme.primary) : theme.textMuted;
|
|
3781
|
+
}
|
|
3782
|
+
}
|
|
3437
3783
|
function redrawFeishuSetupPanel() {
|
|
3438
3784
|
if (!feishuSetupRoot)
|
|
3439
3785
|
return;
|
|
@@ -4340,6 +4686,7 @@ function OpenTuiApp(props) {
|
|
|
4340
4686
|
toggleSidebar,
|
|
4341
4687
|
setSidebarMode: applySidebarMode,
|
|
4342
4688
|
openFeedback,
|
|
4689
|
+
openStats: openStatsPanel,
|
|
4343
4690
|
});
|
|
4344
4691
|
if (!handled)
|
|
4345
4692
|
return false;
|
|
@@ -4518,6 +4865,60 @@ function OpenTuiApp(props) {
|
|
|
4518
4865
|
await openPicker(item.after.mode, item.after.providerId);
|
|
4519
4866
|
}
|
|
4520
4867
|
}
|
|
4868
|
+
function buildLocalModelPickerItems(providerId) {
|
|
4869
|
+
const groups = getVisibleModelProviders(registry, providerId).map((provider) => ({
|
|
4870
|
+
provider,
|
|
4871
|
+
models: localModelsForProvider(registry, provider),
|
|
4872
|
+
}));
|
|
4873
|
+
return buildModelPickerItemsFromGroups(groups, providerId);
|
|
4874
|
+
}
|
|
4875
|
+
async function buildRemoteModelPickerItems(providerId) {
|
|
4876
|
+
const groups = await discoverModelProviderGroups(registry, providerId);
|
|
4877
|
+
return buildModelPickerItemsFromGroups(groups, providerId);
|
|
4878
|
+
}
|
|
4879
|
+
function buildModelPickerItemsFromGroups(groups, providerId) {
|
|
4880
|
+
const items = [];
|
|
4881
|
+
for (const { provider, models } of groups) {
|
|
4882
|
+
for (const model of models) {
|
|
4883
|
+
const reasoningLevels = getModelPickerReasoningLevels(provider.id, model.id);
|
|
4884
|
+
if (reasoningLevels.length > 0) {
|
|
4885
|
+
for (const level of reasoningLevels) {
|
|
4886
|
+
const isCurrent = props.agent.model === `${provider.id}:${model.id}` && props.agent.thinking === level;
|
|
4887
|
+
items.push({
|
|
4888
|
+
label: `${model.name} (${level})`,
|
|
4889
|
+
detail: isCurrent ? "(current)" : undefined,
|
|
4890
|
+
value: `${provider.id}:${model.id}`,
|
|
4891
|
+
command: `/model ${provider.id}:${model.id} --reasoning-effort ${level}`,
|
|
4892
|
+
category: provider.name,
|
|
4893
|
+
gutter: isCurrent ? "●" : undefined,
|
|
4894
|
+
});
|
|
4895
|
+
}
|
|
4896
|
+
continue;
|
|
4897
|
+
}
|
|
4898
|
+
const isCurrent = props.agent.model === `${provider.id}:${model.id}`;
|
|
4899
|
+
items.push({
|
|
4900
|
+
label: model.name,
|
|
4901
|
+
detail: isCurrent ? "(current)" : undefined,
|
|
4902
|
+
value: `${provider.id}:${model.id}`,
|
|
4903
|
+
command: `/model ${provider.id}:${model.id}`,
|
|
4904
|
+
category: provider.name,
|
|
4905
|
+
gutter: isCurrent ? "●" : undefined,
|
|
4906
|
+
});
|
|
4907
|
+
}
|
|
4908
|
+
}
|
|
4909
|
+
const currentModel = props.agent.model;
|
|
4910
|
+
if (!providerId && currentModel && !items.some((item) => item.value === currentModel)) {
|
|
4911
|
+
items.unshift({
|
|
4912
|
+
label: displayModel(currentModel),
|
|
4913
|
+
detail: "(current)",
|
|
4914
|
+
value: currentModel,
|
|
4915
|
+
command: `/model ${currentModel}`,
|
|
4916
|
+
category: "Recent",
|
|
4917
|
+
gutter: "●",
|
|
4918
|
+
});
|
|
4919
|
+
}
|
|
4920
|
+
return items;
|
|
4921
|
+
}
|
|
4521
4922
|
function buildPickerItems(kind, providerId) {
|
|
4522
4923
|
if (kind === "slash")
|
|
4523
4924
|
return [];
|
|
@@ -4526,60 +4927,7 @@ function OpenTuiApp(props) {
|
|
|
4526
4927
|
if (kind === "skill")
|
|
4527
4928
|
return buildSkillItems();
|
|
4528
4929
|
if (kind === "model") {
|
|
4529
|
-
|
|
4530
|
-
for (const provider of registry.getEnabled()) {
|
|
4531
|
-
if (providerId && provider.id !== providerId)
|
|
4532
|
-
continue;
|
|
4533
|
-
const customModels = registry.getModelConfig().getCustomModels(provider.id);
|
|
4534
|
-
const builtinProviderId = provider.id === "openai" && provider.authType === "oauth"
|
|
4535
|
-
? "openai-codex"
|
|
4536
|
-
: provider.id;
|
|
4537
|
-
const models = customModels.length > 0
|
|
4538
|
-
? customModels
|
|
4539
|
-
: listBuiltinModels(builtinProviderId).map((model) => ({
|
|
4540
|
-
id: model.id,
|
|
4541
|
-
name: model.name,
|
|
4542
|
-
providerId: provider.id,
|
|
4543
|
-
}));
|
|
4544
|
-
for (const model of models) {
|
|
4545
|
-
const reasoningLevels = getModelPickerReasoningLevels(provider.id, model.id);
|
|
4546
|
-
if (reasoningLevels.length > 0) {
|
|
4547
|
-
for (const level of reasoningLevels) {
|
|
4548
|
-
const isCurrent = props.agent.model === `${provider.id}:${model.id}` && props.agent.thinking === level;
|
|
4549
|
-
items.push({
|
|
4550
|
-
label: `${model.name} (${level})`,
|
|
4551
|
-
detail: isCurrent ? "(current)" : undefined,
|
|
4552
|
-
value: `${provider.id}:${model.id}`,
|
|
4553
|
-
command: `/model ${provider.id}:${model.id} --reasoning-effort ${level}`,
|
|
4554
|
-
category: provider.name,
|
|
4555
|
-
gutter: isCurrent ? "●" : undefined,
|
|
4556
|
-
});
|
|
4557
|
-
}
|
|
4558
|
-
continue;
|
|
4559
|
-
}
|
|
4560
|
-
const isCurrent = props.agent.model === `${provider.id}:${model.id}`;
|
|
4561
|
-
items.push({
|
|
4562
|
-
label: model.name,
|
|
4563
|
-
detail: isCurrent ? "(current)" : undefined,
|
|
4564
|
-
value: `${provider.id}:${model.id}`,
|
|
4565
|
-
command: `/model ${provider.id}:${model.id}`,
|
|
4566
|
-
category: provider.name,
|
|
4567
|
-
gutter: isCurrent ? "●" : undefined,
|
|
4568
|
-
});
|
|
4569
|
-
}
|
|
4570
|
-
}
|
|
4571
|
-
const currentModel = props.agent.model;
|
|
4572
|
-
if (!providerId && currentModel && !items.some((item) => item.value === currentModel)) {
|
|
4573
|
-
items.unshift({
|
|
4574
|
-
label: displayModel(currentModel),
|
|
4575
|
-
detail: "(current)",
|
|
4576
|
-
value: currentModel,
|
|
4577
|
-
command: `/model ${currentModel}`,
|
|
4578
|
-
category: "Recent",
|
|
4579
|
-
gutter: "●",
|
|
4580
|
-
});
|
|
4581
|
-
}
|
|
4582
|
-
return items;
|
|
4930
|
+
return buildLocalModelPickerItems(providerId);
|
|
4583
4931
|
}
|
|
4584
4932
|
if (kind === "provider") {
|
|
4585
4933
|
return buildProviderConnectItems();
|
|
@@ -4761,6 +5109,15 @@ function OpenTuiApp(props) {
|
|
|
4761
5109
|
redrawTranscript(undefined, nextMessages);
|
|
4762
5110
|
const taskStartedAt = Date.now();
|
|
4763
5111
|
const run = beginAgentRun();
|
|
5112
|
+
traceEvent("tui_agent_run_begin", {
|
|
5113
|
+
runId: run.id,
|
|
5114
|
+
input: summarizeTraceValue(actualInput),
|
|
5115
|
+
displayInput: summarizeTraceValue(displayInput),
|
|
5116
|
+
displayMessages: displayMessages.length,
|
|
5117
|
+
queuedInputs: queuedInputCount(),
|
|
5118
|
+
provider: activeProviderId,
|
|
5119
|
+
model: props.agent.apiModel,
|
|
5120
|
+
}, { surface: "tui" });
|
|
4764
5121
|
let assistantContent = "";
|
|
4765
5122
|
let assistantReasoning = "";
|
|
4766
5123
|
const toolCalls = [];
|
|
@@ -4805,6 +5162,14 @@ function OpenTuiApp(props) {
|
|
|
4805
5162
|
abortSignal: run.abortController.signal,
|
|
4806
5163
|
inputController: run.inputController,
|
|
4807
5164
|
})) {
|
|
5165
|
+
traceEvent("tui_agent_event", {
|
|
5166
|
+
runId: run.id,
|
|
5167
|
+
event: summarizeAgentEventForTrace(event),
|
|
5168
|
+
displayMessages: displayMessages.length,
|
|
5169
|
+
streamingChars: assistantContent.length,
|
|
5170
|
+
reasoningChars: assistantReasoning.length,
|
|
5171
|
+
toolCount: toolCalls.length,
|
|
5172
|
+
}, { surface: "tui" });
|
|
4808
5173
|
if (event.type === "turn_start") {
|
|
4809
5174
|
assistantContent = "";
|
|
4810
5175
|
assistantReasoning = "";
|
|
@@ -5011,6 +5376,11 @@ function OpenTuiApp(props) {
|
|
|
5011
5376
|
if (!runCancelled) {
|
|
5012
5377
|
runError = error?.message || String(error);
|
|
5013
5378
|
}
|
|
5379
|
+
traceEvent("tui_agent_run_error", {
|
|
5380
|
+
runId: run.id,
|
|
5381
|
+
cancelled: runCancelled,
|
|
5382
|
+
error: summarizeTraceError(error),
|
|
5383
|
+
}, { surface: "tui" });
|
|
5014
5384
|
}
|
|
5015
5385
|
finally {
|
|
5016
5386
|
if (pendingStreamingRedrawTimer !== undefined) {
|
|
@@ -5020,8 +5390,19 @@ function OpenTuiApp(props) {
|
|
|
5020
5390
|
pendingApprovalRef = undefined;
|
|
5021
5391
|
setPendingApproval(undefined);
|
|
5022
5392
|
setApprovalOptionIdx(0);
|
|
5393
|
+
traceEvent("tui_agent_run_end", {
|
|
5394
|
+
runId: run.id,
|
|
5395
|
+
cancelled: runCancelled,
|
|
5396
|
+
error: runError,
|
|
5397
|
+
displayMessages: displayMessages.length,
|
|
5398
|
+
queuedInputs: queuedInputCount(),
|
|
5399
|
+
}, { surface: "tui" });
|
|
5023
5400
|
for (const pendingInput of run.inputController.clear()) {
|
|
5024
5401
|
const pendingSteer = removePendingSteerInput(pendingInput.id);
|
|
5402
|
+
if (runCancelled) {
|
|
5403
|
+
removeQueuedUserDisplay(pendingSteer?.displayId);
|
|
5404
|
+
continue;
|
|
5405
|
+
}
|
|
5025
5406
|
requeueRejectedSteer(pendingInput.content, pendingSteer?.displayId);
|
|
5026
5407
|
}
|
|
5027
5408
|
finishAgentRun(run);
|
|
@@ -5035,6 +5416,7 @@ function OpenTuiApp(props) {
|
|
|
5035
5416
|
else if (runCancelled) {
|
|
5036
5417
|
if (!notice())
|
|
5037
5418
|
setNotice("Agent run cancelled");
|
|
5419
|
+
displayMessages = reconstructDisplayMessages(props.agent.messages);
|
|
5038
5420
|
redrawTranscript();
|
|
5039
5421
|
}
|
|
5040
5422
|
else {
|
|
@@ -5074,13 +5456,13 @@ function OpenTuiApp(props) {
|
|
|
5074
5456
|
return h("box", {
|
|
5075
5457
|
ref: (ref) => {
|
|
5076
5458
|
sessionComposerShell = ref;
|
|
5077
|
-
ref.visible = !isHomeSurfaceActive(streamingDisplay) && !pendingQuestion() && !pendingFeedback() && !pendingFeishuSetup();
|
|
5459
|
+
ref.visible = !isHomeSurfaceActive(streamingDisplay) && !pendingQuestion() && !pendingFeedback() && !statsPanel && !pendingFeishuSetup();
|
|
5078
5460
|
},
|
|
5079
5461
|
width: "100%",
|
|
5080
5462
|
paddingLeft: 2,
|
|
5081
5463
|
paddingRight: 2,
|
|
5082
5464
|
flexShrink: 0,
|
|
5083
|
-
visible: !isHomeSurfaceActive(streamingDisplay) && !pendingQuestion() && !pendingFeishuSetup(),
|
|
5465
|
+
visible: !isHomeSurfaceActive(streamingDisplay) && !pendingQuestion() && !statsPanel && !pendingFeishuSetup(),
|
|
5084
5466
|
}, renderPrompt({
|
|
5085
5467
|
ref: (ref) => { sessionPromptRef = ref; },
|
|
5086
5468
|
focused: !isHomeSurfaceActive(streamingDisplay),
|
|
@@ -5091,7 +5473,7 @@ function OpenTuiApp(props) {
|
|
|
5091
5473
|
onKeyDown: handlePickerKey,
|
|
5092
5474
|
onUiKeyDown: promptUiKeyDown,
|
|
5093
5475
|
getText: readPromptText,
|
|
5094
|
-
disabled: () => !!pendingFeedback(),
|
|
5476
|
+
disabled: () => !!pendingFeedback() || !!statsPanel,
|
|
5095
5477
|
mode,
|
|
5096
5478
|
registerModeLabel: registerPromptModeLabel,
|
|
5097
5479
|
registerModelLabel: registerPromptModelLabel,
|
|
@@ -5106,6 +5488,8 @@ function OpenTuiApp(props) {
|
|
|
5106
5488
|
return "Answer the question below";
|
|
5107
5489
|
if (pendingFeedback())
|
|
5108
5490
|
return "Describe feedback below";
|
|
5491
|
+
if (statsPanel)
|
|
5492
|
+
return "Stats panel is open";
|
|
5109
5493
|
const plan = pendingPlan();
|
|
5110
5494
|
if (plan)
|
|
5111
5495
|
return "Press Enter to approve plan or Esc to reject";
|
|
@@ -5132,18 +5516,22 @@ function OpenTuiApp(props) {
|
|
|
5132
5516
|
paddingRight: 2,
|
|
5133
5517
|
}, [
|
|
5134
5518
|
h("box", { flexShrink: 0, flexDirection: "column", alignItems: "center" }, ...logoLines.map((line) => renderHomeLogoLine(line))),
|
|
5519
|
+
h("box", { flexShrink: 0, flexDirection: "column", alignItems: "center", paddingTop: 1 }, h("text", { fg: theme.textMuted, content: `v${getCurrentVersion()}` })),
|
|
5520
|
+
...(props.options.updateNotice
|
|
5521
|
+
? [h("box", { flexShrink: 0, flexDirection: "column", alignItems: "center" }, h("text", { fg: theme.accent, content: props.options.updateNotice }))]
|
|
5522
|
+
: []),
|
|
5135
5523
|
h("box", { height: 1, minHeight: 0, flexShrink: 1 }),
|
|
5136
5524
|
h("box", {
|
|
5137
5525
|
ref: (ref) => {
|
|
5138
5526
|
homeComposerShell = ref;
|
|
5139
|
-
ref.visible = isHomeSurfaceActive(streamingDisplay) && !pendingQuestion() && !pendingFeedback() && !pendingFeishuSetup();
|
|
5527
|
+
ref.visible = isHomeSurfaceActive(streamingDisplay) && !pendingQuestion() && !pendingFeedback() && !statsPanel && !pendingFeishuSetup();
|
|
5140
5528
|
},
|
|
5141
5529
|
width: "100%",
|
|
5142
5530
|
maxWidth: 75,
|
|
5143
5531
|
zIndex: 1000,
|
|
5144
5532
|
paddingTop: 1,
|
|
5145
5533
|
flexShrink: 0,
|
|
5146
|
-
visible: isHomeSurfaceActive(streamingDisplay) && !pendingQuestion() && !pendingFeishuSetup(),
|
|
5534
|
+
visible: isHomeSurfaceActive(streamingDisplay) && !pendingQuestion() && !statsPanel && !pendingFeishuSetup(),
|
|
5147
5535
|
}, renderPrompt({
|
|
5148
5536
|
ref: (ref) => {
|
|
5149
5537
|
homePromptRef = ref;
|
|
@@ -5158,7 +5546,7 @@ function OpenTuiApp(props) {
|
|
|
5158
5546
|
onKeyDown: handlePickerKey,
|
|
5159
5547
|
onUiKeyDown: promptUiKeyDown,
|
|
5160
5548
|
getText: readPromptText,
|
|
5161
|
-
disabled: () => !!pendingFeedback(),
|
|
5549
|
+
disabled: () => !!pendingFeedback() || !!statsPanel,
|
|
5162
5550
|
mode,
|
|
5163
5551
|
registerModeLabel: registerPromptModeLabel,
|
|
5164
5552
|
registerModelLabel: registerPromptModelLabel,
|
|
@@ -5173,6 +5561,8 @@ function OpenTuiApp(props) {
|
|
|
5173
5561
|
return "Answer the question below";
|
|
5174
5562
|
if (pendingFeedback())
|
|
5175
5563
|
return "Describe feedback below";
|
|
5564
|
+
if (statsPanel)
|
|
5565
|
+
return "Stats panel is open";
|
|
5176
5566
|
const plan = pendingPlan();
|
|
5177
5567
|
if (plan)
|
|
5178
5568
|
return "Press Enter to approve plan or Esc to reject";
|
|
@@ -5419,6 +5809,131 @@ function OpenTuiApp(props) {
|
|
|
5419
5809
|
content: "ctrl+d submit · tab view payload · enter newline · esc cancel",
|
|
5420
5810
|
})));
|
|
5421
5811
|
}
|
|
5812
|
+
function renderStatsPanel() {
|
|
5813
|
+
return h("box", {
|
|
5814
|
+
ref: (ref) => {
|
|
5815
|
+
statsRoot = ref;
|
|
5816
|
+
redrawStatsPanel();
|
|
5817
|
+
},
|
|
5818
|
+
visible: false,
|
|
5819
|
+
focusable: true,
|
|
5820
|
+
position: "absolute",
|
|
5821
|
+
left: 0,
|
|
5822
|
+
top: 0,
|
|
5823
|
+
width: "100%",
|
|
5824
|
+
height: "100%",
|
|
5825
|
+
zIndex: 3050,
|
|
5826
|
+
backgroundColor: modalBackdropColor(),
|
|
5827
|
+
flexDirection: "column",
|
|
5828
|
+
onKeyDown: (event) => {
|
|
5829
|
+
if (handleStatsKey(event)) {
|
|
5830
|
+
event.preventDefault?.();
|
|
5831
|
+
event.stopPropagation?.();
|
|
5832
|
+
return true;
|
|
5833
|
+
}
|
|
5834
|
+
return false;
|
|
5835
|
+
},
|
|
5836
|
+
onMouseUp: () => closeStatsPanel(),
|
|
5837
|
+
}, h("box", {
|
|
5838
|
+
ref: (ref) => {
|
|
5839
|
+
statsPanelBox = ref;
|
|
5840
|
+
redrawStatsPanel();
|
|
5841
|
+
},
|
|
5842
|
+
visible: false,
|
|
5843
|
+
position: "absolute",
|
|
5844
|
+
width: 76,
|
|
5845
|
+
height: 24,
|
|
5846
|
+
backgroundColor: theme.backgroundPanel,
|
|
5847
|
+
flexDirection: "column",
|
|
5848
|
+
paddingTop: 1,
|
|
5849
|
+
onMouseUp: (event) => {
|
|
5850
|
+
event.stopPropagation?.();
|
|
5851
|
+
},
|
|
5852
|
+
}, [
|
|
5853
|
+
h("box", {
|
|
5854
|
+
flexDirection: "row",
|
|
5855
|
+
alignItems: "center",
|
|
5856
|
+
justifyContent: "space-between",
|
|
5857
|
+
paddingLeft: 4,
|
|
5858
|
+
paddingRight: 4,
|
|
5859
|
+
flexShrink: 0,
|
|
5860
|
+
}, h("text", {
|
|
5861
|
+
ref: (ref) => { statsTitle = ref; },
|
|
5862
|
+
fg: theme.text,
|
|
5863
|
+
content: "Stats",
|
|
5864
|
+
}), h("text", {
|
|
5865
|
+
ref: (ref) => { statsEsc = ref; },
|
|
5866
|
+
fg: theme.textMuted,
|
|
5867
|
+
content: "esc",
|
|
5868
|
+
onMouseUp: () => closeStatsPanel(),
|
|
5869
|
+
})),
|
|
5870
|
+
h("box", {
|
|
5871
|
+
flexDirection: "row",
|
|
5872
|
+
gap: 1,
|
|
5873
|
+
paddingLeft: 4,
|
|
5874
|
+
paddingRight: 4,
|
|
5875
|
+
paddingTop: 1,
|
|
5876
|
+
flexShrink: 0,
|
|
5877
|
+
}, h("box", {
|
|
5878
|
+
ref: (ref) => { statsTab7Box = ref; },
|
|
5879
|
+
paddingLeft: 1,
|
|
5880
|
+
paddingRight: 1,
|
|
5881
|
+
backgroundColor: theme.backgroundElement,
|
|
5882
|
+
onMouseUp: () => setStatsRange("7d"),
|
|
5883
|
+
}, h("text", {
|
|
5884
|
+
ref: (ref) => { statsTab7Text = ref; },
|
|
5885
|
+
fg: theme.textMuted,
|
|
5886
|
+
content: "7 days",
|
|
5887
|
+
})), h("box", {
|
|
5888
|
+
ref: (ref) => { statsTab30Box = ref; },
|
|
5889
|
+
paddingLeft: 1,
|
|
5890
|
+
paddingRight: 1,
|
|
5891
|
+
backgroundColor: theme.primary,
|
|
5892
|
+
onMouseUp: () => setStatsRange("30d"),
|
|
5893
|
+
}, h("text", {
|
|
5894
|
+
ref: (ref) => { statsTab30Text = ref; },
|
|
5895
|
+
fg: contrastText(theme.primary),
|
|
5896
|
+
content: "30 days",
|
|
5897
|
+
}))),
|
|
5898
|
+
h("box", {
|
|
5899
|
+
paddingLeft: 4,
|
|
5900
|
+
paddingRight: 4,
|
|
5901
|
+
paddingTop: 1,
|
|
5902
|
+
flexGrow: 1,
|
|
5903
|
+
minHeight: 0,
|
|
5904
|
+
}, h("scrollbox", {
|
|
5905
|
+
ref: (ref) => { statsBodyScroll = ref; },
|
|
5906
|
+
flexGrow: 1,
|
|
5907
|
+
minHeight: 0,
|
|
5908
|
+
height: 14,
|
|
5909
|
+
onMouseScroll: (event) => {
|
|
5910
|
+
event.stopPropagation?.();
|
|
5911
|
+
},
|
|
5912
|
+
}, h("text", {
|
|
5913
|
+
ref: (ref) => { statsBodyText = ref; },
|
|
5914
|
+
fg: theme.text,
|
|
5915
|
+
wrapMode: "none",
|
|
5916
|
+
content: "",
|
|
5917
|
+
}))),
|
|
5918
|
+
h("box", {
|
|
5919
|
+
flexDirection: "row",
|
|
5920
|
+
justifyContent: "space-between",
|
|
5921
|
+
paddingLeft: 4,
|
|
5922
|
+
paddingRight: 4,
|
|
5923
|
+
paddingTop: 1,
|
|
5924
|
+
paddingBottom: 1,
|
|
5925
|
+
flexShrink: 0,
|
|
5926
|
+
backgroundColor: theme.backgroundPanel,
|
|
5927
|
+
}, h("text", {
|
|
5928
|
+
ref: (ref) => { statsFooterText = ref; },
|
|
5929
|
+
fg: theme.textMuted,
|
|
5930
|
+
bg: theme.backgroundPanel,
|
|
5931
|
+
wrapMode: "none",
|
|
5932
|
+
truncate: true,
|
|
5933
|
+
content: "left/right:range|tab:toggle|esc:close|view:30d",
|
|
5934
|
+
})),
|
|
5935
|
+
]));
|
|
5936
|
+
}
|
|
5422
5937
|
function renderFeishuSetupPanel() {
|
|
5423
5938
|
return h("box", {
|
|
5424
5939
|
ref: (ref) => {
|
|
@@ -5507,7 +6022,8 @@ function OpenTuiApp(props) {
|
|
|
5507
6022
|
width: "100%",
|
|
5508
6023
|
value: "",
|
|
5509
6024
|
placeholder: "",
|
|
5510
|
-
|
|
6025
|
+
textColor: theme.text,
|
|
6026
|
+
focusedTextColor: theme.text,
|
|
5511
6027
|
backgroundColor: theme.backgroundElement,
|
|
5512
6028
|
focusedBackgroundColor: theme.backgroundElement,
|
|
5513
6029
|
cursorColor: theme.primary,
|
|
@@ -5652,7 +6168,7 @@ function OpenTuiApp(props) {
|
|
|
5652
6168
|
},
|
|
5653
6169
|
visible: false,
|
|
5654
6170
|
position: "absolute",
|
|
5655
|
-
width:
|
|
6171
|
+
width: 76,
|
|
5656
6172
|
height: PROVIDER_DIALOG_ROWS + 7,
|
|
5657
6173
|
backgroundColor: theme.backgroundPanel,
|
|
5658
6174
|
flexDirection: "column",
|
|
@@ -5689,7 +6205,8 @@ function OpenTuiApp(props) {
|
|
|
5689
6205
|
width: "100%",
|
|
5690
6206
|
value: "",
|
|
5691
6207
|
placeholder: "Search",
|
|
5692
|
-
|
|
6208
|
+
textColor: theme.text,
|
|
6209
|
+
focusedTextColor: theme.text,
|
|
5693
6210
|
backgroundColor: theme.backgroundPanel,
|
|
5694
6211
|
focusedBackgroundColor: theme.backgroundPanel,
|
|
5695
6212
|
cursorColor: theme.primary,
|
|
@@ -5702,15 +6219,11 @@ function OpenTuiApp(props) {
|
|
|
5702
6219
|
providerDialog = { ...state, apiKey: value, error: undefined };
|
|
5703
6220
|
}
|
|
5704
6221
|
else {
|
|
6222
|
+
const query = value.trim().toLowerCase();
|
|
5705
6223
|
const items = providerDialogItemsFor(state.step, state.providerId).filter((item) => {
|
|
5706
|
-
const query = value.trim().toLowerCase();
|
|
5707
6224
|
if (!query)
|
|
5708
6225
|
return true;
|
|
5709
|
-
|
|
5710
|
-
.filter(Boolean)
|
|
5711
|
-
.join(" ")
|
|
5712
|
-
.toLowerCase();
|
|
5713
|
-
return haystack.includes(query) || fuzzyMatch(haystack, query);
|
|
6226
|
+
return providerDialogMatchScore(item, query) > 0;
|
|
5714
6227
|
});
|
|
5715
6228
|
providerDialog = {
|
|
5716
6229
|
...state,
|
|
@@ -6293,6 +6806,7 @@ function OpenTuiApp(props) {
|
|
|
6293
6806
|
registerTraceBadge: registerFooterTraceBadge,
|
|
6294
6807
|
}),
|
|
6295
6808
|
renderProviderDialog(),
|
|
6809
|
+
renderStatsPanel(),
|
|
6296
6810
|
renderFeishuSetupPanel(),
|
|
6297
6811
|
renderNoticeOverlay(),
|
|
6298
6812
|
]);
|
|
@@ -6564,7 +7078,7 @@ function renderMarkdownContent(content, syntaxStyle, options) {
|
|
|
6564
7078
|
bg: theme.background,
|
|
6565
7079
|
width: "100%",
|
|
6566
7080
|
tableOptions: {
|
|
6567
|
-
widthMode: "
|
|
7081
|
+
widthMode: "content",
|
|
6568
7082
|
columnFitter: "balanced",
|
|
6569
7083
|
wrapMode: "word",
|
|
6570
7084
|
cellPadding: 1,
|
|
@@ -6784,7 +7298,13 @@ function syncMarkdownRenderable(markdown, content, streaming) {
|
|
|
6784
7298
|
return;
|
|
6785
7299
|
markdown.content = content;
|
|
6786
7300
|
markdown.streaming = streaming;
|
|
6787
|
-
markdown
|
|
7301
|
+
// While streaming, let OpenTUI's incremental markdown/code-block rendering do
|
|
7302
|
+
// its job — clearing the parse cache every delta forces the (syntax-
|
|
7303
|
+
// highlighted) code blocks to be rebuilt and re-highlighted on every token,
|
|
7304
|
+
// which is the source of the visible flicker on streamed code blocks. Clear
|
|
7305
|
+
// the cache only once streaming ends, to fully reparse the finalized content.
|
|
7306
|
+
if (!streaming)
|
|
7307
|
+
markdown.clearCache();
|
|
6788
7308
|
}
|
|
6789
7309
|
function updateAssistantPartEntries(entry, parts, options, streaming) {
|
|
6790
7310
|
const partsBox = entry.refs.partsBox;
|
|
@@ -6952,7 +7472,7 @@ function createTraceGroupRenderable(ctx, group, syntaxStyle, width = 80) {
|
|
|
6952
7472
|
}, children);
|
|
6953
7473
|
}
|
|
6954
7474
|
function shouldRenderTraceGroupAsRawTool(tool) {
|
|
6955
|
-
return tool.name === "question" || tool.name === "todo_write" || tool.name === "edit";
|
|
7475
|
+
return tool.name === "question" || tool.name === "todo_write" || tool.name === "edit" || tool.name === "apply_patch";
|
|
6956
7476
|
}
|
|
6957
7477
|
function traceGroupDetailLines(group) {
|
|
6958
7478
|
return group.previewLines.length > 0 ? group.previewLines : group.items;
|
|
@@ -7033,8 +7553,9 @@ function traceGroupRenderableSignature(group) {
|
|
|
7033
7553
|
return [
|
|
7034
7554
|
tool.id,
|
|
7035
7555
|
tool.name,
|
|
7036
|
-
tool.status ?? (tool.result === undefined ? "pending" : "completed"),
|
|
7556
|
+
tool.status ?? (tool.result === undefined && !tool.resultCollapsed ? "pending" : "completed"),
|
|
7037
7557
|
tool.isError ? "error" : "ok",
|
|
7558
|
+
tool.resultCollapsed ? "collapsed" : "expanded",
|
|
7038
7559
|
stableStringify(tool.args),
|
|
7039
7560
|
tool.result ?? "",
|
|
7040
7561
|
stableStringify(tool.metadata ?? null),
|
|
@@ -7046,8 +7567,9 @@ function toolRenderableSignature(tool, writeExpanded) {
|
|
|
7046
7567
|
return [
|
|
7047
7568
|
tool.id,
|
|
7048
7569
|
tool.name,
|
|
7049
|
-
tool.status ?? (tool.result === undefined ? "pending" : "completed"),
|
|
7570
|
+
tool.status ?? (tool.result === undefined && !tool.resultCollapsed ? "pending" : "completed"),
|
|
7050
7571
|
tool.isError ? "error" : "ok",
|
|
7572
|
+
tool.resultCollapsed ? "collapsed" : "expanded",
|
|
7051
7573
|
tool.streamingArgs ? "streaming-args" : "args-complete",
|
|
7052
7574
|
writeExpanded ? "expanded" : "collapsed",
|
|
7053
7575
|
hashString(stableStringify(tool.args)),
|
|
@@ -7121,7 +7643,7 @@ function createMarkdown(ctx, content, syntaxStyle, options) {
|
|
|
7121
7643
|
width: "100%",
|
|
7122
7644
|
flexShrink: 0,
|
|
7123
7645
|
tableOptions: {
|
|
7124
|
-
widthMode: "
|
|
7646
|
+
widthMode: "content",
|
|
7125
7647
|
columnFitter: "balanced",
|
|
7126
7648
|
wrapMode: "word",
|
|
7127
7649
|
cellPadding: 1,
|
|
@@ -7174,16 +7696,41 @@ function createMarkdownList(ctx, token, palette, defaultFg) {
|
|
|
7174
7696
|
flexShrink: 0,
|
|
7175
7697
|
}, items.map((item, index) => {
|
|
7176
7698
|
const marker = ordered ? `${start + index}. ` : "• ";
|
|
7177
|
-
return
|
|
7178
|
-
fg(theme.textMuted)(marker),
|
|
7179
|
-
...markdownInlineToStyledText(markdownTokenInlineTokens(item), palette, item.text ?? "").chunks,
|
|
7180
|
-
]), {
|
|
7181
|
-
fg: defaultFg,
|
|
7182
|
-
wrapMode: "word",
|
|
7183
|
-
flexShrink: 0,
|
|
7184
|
-
});
|
|
7699
|
+
return createMarkdownListItem(ctx, item, marker, palette, defaultFg);
|
|
7185
7700
|
}));
|
|
7186
7701
|
}
|
|
7702
|
+
function createMarkdownListItem(ctx, item, marker, palette, defaultFg) {
|
|
7703
|
+
const tokens = Array.isArray(item?.tokens) ? item.tokens : [];
|
|
7704
|
+
const inlineTokens = tokens.filter((child) => !isMarkdownListToken(child));
|
|
7705
|
+
const nestedLists = tokens.filter(isMarkdownListToken);
|
|
7706
|
+
const fallback = tokens.length > 0 ? "" : (item?.text ?? "");
|
|
7707
|
+
const children = [];
|
|
7708
|
+
const line = markdownInlineToStyledText(inlineTokens, palette, fallback);
|
|
7709
|
+
children.push(createText(ctx, new StyledText([
|
|
7710
|
+
fg(theme.textMuted)(marker),
|
|
7711
|
+
...line.chunks,
|
|
7712
|
+
]), {
|
|
7713
|
+
fg: defaultFg,
|
|
7714
|
+
wrapMode: "word",
|
|
7715
|
+
flexShrink: 0,
|
|
7716
|
+
}));
|
|
7717
|
+
for (const nestedList of nestedLists) {
|
|
7718
|
+
const nested = createMarkdownList(ctx, nestedList, palette, defaultFg);
|
|
7719
|
+
if (!nested)
|
|
7720
|
+
continue;
|
|
7721
|
+
children.push(createBox(ctx, {
|
|
7722
|
+
flexDirection: "column",
|
|
7723
|
+
flexShrink: 0,
|
|
7724
|
+
paddingLeft: Math.max(2, marker.length),
|
|
7725
|
+
}, [nested]));
|
|
7726
|
+
}
|
|
7727
|
+
return children.length === 1
|
|
7728
|
+
? children[0]
|
|
7729
|
+
: createBox(ctx, { flexDirection: "column", flexShrink: 0 }, children);
|
|
7730
|
+
}
|
|
7731
|
+
function isMarkdownListToken(token) {
|
|
7732
|
+
return token?.type === "list";
|
|
7733
|
+
}
|
|
7187
7734
|
function markdownTokenInlineTokens(token) {
|
|
7188
7735
|
if (Array.isArray(token?.tokens))
|
|
7189
7736
|
return token.tokens;
|
|
@@ -7775,10 +8322,10 @@ function renderTool(tool, syntaxStyle, width = 80) {
|
|
|
7775
8322
|
const icon = toolStateIcon(tool);
|
|
7776
8323
|
const color = toolColor(tool);
|
|
7777
8324
|
const diff = extractToolDiff(tool);
|
|
7778
|
-
if (diff && !tool.isError && tool.name === "edit") {
|
|
7779
|
-
return h("box", { paddingLeft: 3, marginTop: 1, flexDirection: "column", flexShrink: 0 }, h("text", { fg: color }, `${icon} ${displayToolName(tool.name)}${toolHeader(tool) ? ` ${toolHeader(tool)}` : ""}`), h("box", { paddingLeft: 1, marginTop: 1, border: ["left"], borderColor: theme.borderSubtle, flexDirection: "column", flexShrink: 0 }, renderDiffContent(diff, toolPath(tool), syntaxStyle, width)));
|
|
8325
|
+
if (diff && !tool.resultCollapsed && !tool.isError && (tool.name === "edit" || tool.name === "apply_patch")) {
|
|
8326
|
+
return h("box", { paddingLeft: 3, marginTop: 1, flexDirection: "column", flexShrink: 0 }, h("text", { fg: color }, `${icon} ${displayToolName(tool.name)}${toolHeader(tool) ? ` ${toolHeader(tool)}` : ""}`), h("box", { paddingLeft: 1, marginTop: 1, border: ["left"], borderColor: theme.borderSubtle, flexDirection: "column", flexShrink: 0, backgroundColor: theme.diffContextBg }, renderDiffContent(diff, toolPath(tool), syntaxStyle, width)));
|
|
7780
8327
|
}
|
|
7781
|
-
if (isWritePreviewTool(tool)) {
|
|
8328
|
+
if (!tool.resultCollapsed && isWritePreviewTool(tool)) {
|
|
7782
8329
|
const hasContent = typeof tool.args.content === "string";
|
|
7783
8330
|
const contentStr = hasContent ? String(tool.args.content) : "";
|
|
7784
8331
|
const preview = hasContent ? formatWritePreview(contentStr, false) : null;
|
|
@@ -7884,10 +8431,18 @@ function pickerTitle(kind, providerId) {
|
|
|
7884
8431
|
}
|
|
7885
8432
|
}
|
|
7886
8433
|
function getModelPickerReasoningLevels(providerId, modelId) {
|
|
7887
|
-
|
|
8434
|
+
// Only expand into one picker row per effort for models that genuinely have a
|
|
8435
|
+
// reasoning-effort spectrum: OpenAI's reasoning models (codex gpt-5.x:
|
|
8436
|
+
// off/minimal/low/medium/high/xhigh) and DeepSeek's v4 models. Other providers
|
|
8437
|
+
// (e.g. GLM, Moonshot/Kimi) only have a thinking on/off toggle, not an effort
|
|
8438
|
+
// control, so they stay as a single row.
|
|
8439
|
+
const isOpenAIReasoning = providerId === "openai" || providerId === "openai-codex";
|
|
8440
|
+
const isDeepseekReasoning = providerId === "deepseek" && (modelId === "deepseek-v4-flash" || modelId === "deepseek-v4-pro");
|
|
8441
|
+
if (!isOpenAIReasoning && !isDeepseekReasoning)
|
|
7888
8442
|
return [];
|
|
7889
|
-
|
|
7890
|
-
|
|
8443
|
+
const levels = getAvailableThinkingLevels(providerId, modelId);
|
|
8444
|
+
// gpt-4o and friends report only ["off"] — keep those as a single row too.
|
|
8445
|
+
return levels.length > 1 ? levels : [];
|
|
7891
8446
|
}
|
|
7892
8447
|
function displayModelWithThinking(model, thinkingLevel) {
|
|
7893
8448
|
if (!model)
|
|
@@ -7895,7 +8450,10 @@ function displayModelWithThinking(model, thinkingLevel) {
|
|
|
7895
8450
|
const { providerId, modelId } = decodeModel(model);
|
|
7896
8451
|
if (!providerId)
|
|
7897
8452
|
return displayModel(model);
|
|
7898
|
-
|
|
8453
|
+
// Use the same scoping as the picker: only models with a real reasoning-effort
|
|
8454
|
+
// spectrum (OpenAI codex gpt-5.x, deepseek v4) get the "(level)" suffix. The
|
|
8455
|
+
// on/off thinking toggle on GLM / Moonshot(Kimi) is not an effort control.
|
|
8456
|
+
const levels = getModelPickerReasoningLevels(providerId, modelId);
|
|
7899
8457
|
if (levels.length > 1 && thinkingLevel !== "off") {
|
|
7900
8458
|
return `${displayModel(model)} (${thinkingLevel})`;
|
|
7901
8459
|
}
|
|
@@ -8237,7 +8795,7 @@ function appendRawToolTranscript(chunks, tool) {
|
|
|
8237
8795
|
append(`${content}\n`, color);
|
|
8238
8796
|
};
|
|
8239
8797
|
appendLine("");
|
|
8240
|
-
const icon = tool.name === "bash" ? "$" : tool.name === "edit" || tool.name === "write" ? "✎" : "●";
|
|
8798
|
+
const icon = tool.name === "bash" ? "$" : tool.name === "edit" || tool.name === "write" || tool.name === "apply_patch" ? "✎" : "●";
|
|
8241
8799
|
const color = toolColor(tool);
|
|
8242
8800
|
append(` ${icon} `, color);
|
|
8243
8801
|
append(displayToolName(tool.name), color);
|
|
@@ -8405,6 +8963,18 @@ function getApprovalPanelMeta(request) {
|
|
|
8405
8963
|
path: request.path,
|
|
8406
8964
|
};
|
|
8407
8965
|
}
|
|
8966
|
+
if (request.type === "patch") {
|
|
8967
|
+
return {
|
|
8968
|
+
icon: "→",
|
|
8969
|
+
title: `Patch ${path}`,
|
|
8970
|
+
subtitle: `${request.paths.length} file${request.paths.length === 1 ? "" : "s"}`,
|
|
8971
|
+
preview: request.diff || "No diff provided",
|
|
8972
|
+
previewHeight: 10,
|
|
8973
|
+
previewColor: request.diff ? theme.toolText : theme.textMuted,
|
|
8974
|
+
diff: request.diff,
|
|
8975
|
+
path: request.paths[0] ?? request.path,
|
|
8976
|
+
};
|
|
8977
|
+
}
|
|
8408
8978
|
return {
|
|
8409
8979
|
icon: "→",
|
|
8410
8980
|
title: `Write ${path}`,
|
|
@@ -8483,7 +9053,7 @@ function toolColor(tool) {
|
|
|
8483
9053
|
return theme.toolShell;
|
|
8484
9054
|
if (tool.name === "read")
|
|
8485
9055
|
return theme.toolRead;
|
|
8486
|
-
if (tool.name === "write" || tool.name === "edit")
|
|
9056
|
+
if (tool.name === "write" || tool.name === "edit" || tool.name === "apply_patch")
|
|
8487
9057
|
return theme.toolWrite;
|
|
8488
9058
|
if (tool.name === "grep" || tool.name === "glob" || tool.name === "web_search" || tool.name === "web_fetch")
|
|
8489
9059
|
return theme.toolSearch;
|
|
@@ -8494,6 +9064,7 @@ function displayToolName(name) {
|
|
|
8494
9064
|
read: "Read",
|
|
8495
9065
|
write: "Write",
|
|
8496
9066
|
edit: "Edit",
|
|
9067
|
+
apply_patch: "Patch",
|
|
8497
9068
|
bash: "Shell",
|
|
8498
9069
|
grep: "Grep",
|
|
8499
9070
|
glob: "Glob",
|
|
@@ -8526,16 +9097,36 @@ function toolHeader(tool) {
|
|
|
8526
9097
|
const agentId = args.agent_id ?? (Array.isArray(args.agent_ids) ? `${args.agent_ids.length} agents` : undefined);
|
|
8527
9098
|
return agentId ? `(${truncate(String(agentId), 64)})` : "";
|
|
8528
9099
|
}
|
|
8529
|
-
const value = args.path ?? args.command ?? args.pattern ?? args.url ?? args.query;
|
|
9100
|
+
const value = args.path ?? args.command ?? args.pattern ?? args.url ?? args.query ?? toolPath(tool);
|
|
8530
9101
|
return value ? `(${truncate(String(value).replace(/\n/g, " "), 64)})` : "";
|
|
8531
9102
|
}
|
|
8532
9103
|
function toolPath(tool) {
|
|
8533
|
-
const value = tool.args?.path
|
|
9104
|
+
const value = tool.args?.path
|
|
9105
|
+
?? tool.args?.filePath
|
|
9106
|
+
?? tool.metadata?.path
|
|
9107
|
+
?? (Array.isArray(tool.metadata?.paths) ? tool.metadata.paths[0] : undefined);
|
|
8534
9108
|
return typeof value === "string" ? value : undefined;
|
|
8535
9109
|
}
|
|
9110
|
+
// Strip only leading/trailing newlines — NOT a full .trim(). A blank context
|
|
9111
|
+
// line in a unified diff is a single space (" "); plain .trim() would delete a
|
|
9112
|
+
// trailing blank context line, leaving the hunk body shorter than its @@ header
|
|
9113
|
+
// count and breaking the diff parser ("Added line count did not match").
|
|
9114
|
+
function stripDiffEdgeNewlines(diff) {
|
|
9115
|
+
return diff.replace(/^\n+/, "").replace(/\n+$/, "");
|
|
9116
|
+
}
|
|
8536
9117
|
function extractToolDiff(tool) {
|
|
9118
|
+
if (tool.resultCollapsed)
|
|
9119
|
+
return undefined;
|
|
9120
|
+
if (typeof tool.metadata?.diff === "string" && tool.metadata.diff.trim().length > 0) {
|
|
9121
|
+
return stripDiffEdgeNewlines(tool.metadata.diff);
|
|
9122
|
+
}
|
|
8537
9123
|
if (!tool.result)
|
|
8538
9124
|
return undefined;
|
|
9125
|
+
if (tool.result.includes("✂") ||
|
|
9126
|
+
tool.result.includes("chars truncated") ||
|
|
9127
|
+
tool.result.includes("chars omitted for UI")) {
|
|
9128
|
+
return undefined;
|
|
9129
|
+
}
|
|
8539
9130
|
const marker = "\n\nDiff:\n";
|
|
8540
9131
|
const index = tool.result.indexOf(marker);
|
|
8541
9132
|
if (index === -1)
|
|
@@ -8543,10 +9134,14 @@ function extractToolDiff(tool) {
|
|
|
8543
9134
|
const rawDiff = tool.result.slice(index + marker.length);
|
|
8544
9135
|
const diagnosticsIndex = rawDiff.search(/\n\nLSP diagnostics in /);
|
|
8545
9136
|
const diff = diagnosticsIndex === -1 ? rawDiff : rawDiff.slice(0, diagnosticsIndex);
|
|
8546
|
-
return diff.trim().length > 0 ? diff : undefined;
|
|
9137
|
+
return diff.trim().length > 0 ? stripDiffEdgeNewlines(diff) : undefined;
|
|
8547
9138
|
}
|
|
8548
|
-
function diffViewMode(
|
|
8549
|
-
|
|
9139
|
+
function diffViewMode(_width = 80) {
|
|
9140
|
+
// Always unified: split view pads the shorter side with empty filler rows that
|
|
9141
|
+
// OpenTUI's DiffRenderable leaves uncolored, which shows up as bright white
|
|
9142
|
+
// blocks in light mode. Unified view has no filler rows — every line is
|
|
9143
|
+
// add/remove/context and gets a background — so the edit area stays uniform.
|
|
9144
|
+
return "unified";
|
|
8550
9145
|
}
|
|
8551
9146
|
function filetype(filePath) {
|
|
8552
9147
|
if (!filePath)
|
|
@@ -8589,7 +9184,7 @@ function summarizeToolResult(tool) {
|
|
|
8589
9184
|
const matches = typeof tool.metadata?.matches === "number" ? tool.metadata.matches : undefined;
|
|
8590
9185
|
if (tool.name === "read")
|
|
8591
9186
|
return "";
|
|
8592
|
-
if (tool.name === "edit")
|
|
9187
|
+
if (tool.name === "edit" || tool.name === "apply_patch")
|
|
8593
9188
|
return "patched file";
|
|
8594
9189
|
if (tool.name === "write")
|
|
8595
9190
|
return "wrote file";
|
|
@@ -8615,7 +9210,7 @@ function toolStateIcon(tool) {
|
|
|
8615
9210
|
}
|
|
8616
9211
|
if (tool.name === "bash")
|
|
8617
9212
|
return "$";
|
|
8618
|
-
if (tool.name === "edit")
|
|
9213
|
+
if (tool.name === "edit" || tool.name === "apply_patch")
|
|
8619
9214
|
return "✎";
|
|
8620
9215
|
if (tool.name === "write")
|
|
8621
9216
|
return "✎";
|
|
@@ -8692,7 +9287,7 @@ function formatQuestionAnswer(answer) {
|
|
|
8692
9287
|
return answer?.length ? answer.join(", ") : "(no answer)";
|
|
8693
9288
|
}
|
|
8694
9289
|
function isToolFinished(tool) {
|
|
8695
|
-
return tool.status === "completed" || tool.status === "error" || tool.result !== undefined;
|
|
9290
|
+
return tool.status === "completed" || tool.status === "error" || tool.resultCollapsed === true || tool.result !== undefined;
|
|
8696
9291
|
}
|
|
8697
9292
|
function assistantStatusLabel(message) {
|
|
8698
9293
|
if (message.status === "responding")
|