@bubblebrain-ai/bubble 0.0.4 → 0.0.5
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/budget-ledger.d.ts +20 -0
- package/dist/agent/budget-ledger.js +51 -0
- package/dist/agent/execution-governor.js +1 -1
- package/dist/agent/profiles.d.ts +59 -0
- package/dist/agent/profiles.js +460 -0
- package/dist/agent/subagent-control.d.ts +52 -0
- package/dist/agent/subagent-control.js +38 -0
- package/dist/agent.d.ts +60 -1
- package/dist/agent.js +602 -53
- package/dist/context/budget.js +1 -0
- package/dist/context/compact-llm.js +7 -6
- package/dist/context/compact.js +6 -6
- package/dist/context/projector.d.ts +3 -3
- package/dist/context/projector.js +32 -18
- package/dist/context/prune.d.ts +2 -2
- package/dist/context/prune.js +1 -4
- package/dist/main.js +12 -5
- package/dist/mcp/manager.js +1 -0
- package/dist/orchestrator/default-hooks.js +48 -9
- package/dist/orchestrator/hooks.d.ts +5 -0
- package/dist/prompt/compose.d.ts +1 -0
- package/dist/prompt/compose.js +8 -1
- package/dist/prompt/environment.js +21 -2
- package/dist/prompt/reminders.d.ts +3 -1
- package/dist/prompt/reminders.js +23 -4
- package/dist/prompt/runtime.d.ts +1 -1
- package/dist/prompt/runtime.js +1 -1
- package/dist/provider-artifacts.d.ts +7 -0
- package/dist/provider-artifacts.js +60 -0
- package/dist/provider.d.ts +6 -7
- package/dist/provider.js +77 -15
- package/dist/session-log.js +3 -1
- package/dist/system-prompt.d.ts +2 -0
- package/dist/tools/agent-lifecycle.d.ts +6 -0
- package/dist/tools/agent-lifecycle.js +355 -0
- package/dist/tools/bash.js +2 -0
- package/dist/tools/edit-apply.d.ts +25 -0
- package/dist/tools/edit-apply.js +197 -0
- package/dist/tools/edit.js +63 -56
- package/dist/tools/exit-plan-mode.js +3 -1
- package/dist/tools/file-mutation-queue.d.ts +1 -0
- package/dist/tools/file-mutation-queue.js +32 -0
- package/dist/tools/glob.js +1 -0
- package/dist/tools/grep.js +1 -0
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +3 -3
- package/dist/tools/lsp.js +2 -0
- package/dist/tools/memory.js +2 -0
- package/dist/tools/question.js +2 -0
- package/dist/tools/read.js +1 -0
- package/dist/tools/skill.js +1 -0
- package/dist/tools/task.js +1 -0
- package/dist/tools/todo.js +1 -0
- package/dist/tools/tool-search.js +2 -1
- package/dist/tools/web-fetch.js +1 -0
- package/dist/tools/web-search.js +1 -0
- package/dist/tools/write.js +2 -0
- package/dist/tui/display-history.d.ts +8 -1
- package/dist/tui/markdown-inline.d.ts +22 -0
- package/dist/tui/markdown-inline.js +68 -0
- package/dist/tui/render-signature.d.ts +1 -0
- package/dist/tui/render-signature.js +7 -0
- package/dist/tui/run.js +712 -267
- package/dist/tui/tool-renderers/fallback.d.ts +2 -0
- package/dist/tui/tool-renderers/fallback.js +75 -0
- package/dist/tui/tool-renderers/registry.d.ts +3 -0
- package/dist/tui/tool-renderers/registry.js +11 -0
- package/dist/tui/tool-renderers/subagent.d.ts +2 -0
- package/dist/tui/tool-renderers/subagent.js +114 -0
- package/dist/tui/tool-renderers/types.d.ts +36 -0
- package/dist/tui/tool-renderers/types.js +1 -0
- package/dist/tui/tool-renderers/write-preview.d.ts +12 -0
- package/dist/tui/tool-renderers/write-preview.js +22 -0
- package/dist/tui/tool-renderers/write.d.ts +6 -0
- package/dist/tui/tool-renderers/write.js +82 -0
- package/dist/types.d.ts +90 -10
- package/package.json +1 -1
package/dist/tui/run.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BoxRenderable, CodeRenderable, createCliRenderer, DiffRenderable, getTreeSitterClient, MarkdownRenderable, LineNumberRenderable, StyledText, RGBA, fg, bg, bold, dim, TextAttributes, TextRenderable, } from "@opentui/core";
|
|
1
|
+
import { BoxRenderable, CodeRenderable, createCliRenderer, DiffRenderable, getTreeSitterClient, MarkdownRenderable, LineNumberRenderable, StyledText, RGBA, fg, bg, bold, italic, dim, TextAttributes, TextRenderable, } from "@opentui/core";
|
|
2
2
|
import { createComponent, createElement, insert, render, spread, useKeyboard, useRenderer, useSelectionHandler, useTerminalDimensions, } from "@opentui/solid";
|
|
3
3
|
import { createEffect, createSignal, onCleanup, onMount } from "solid-js";
|
|
4
4
|
import { AgentAbortError } from "../agent.js";
|
|
@@ -13,6 +13,11 @@ import { sidebarMcpRowsFromStates, renderMcpRowMarker } from "./sidebar-mcp.js";
|
|
|
13
13
|
import { expandAtMentions, filterFileSuggestions, findAtContext, listProjectFiles } from "./file-mentions.js";
|
|
14
14
|
import { compactDisplayMessages } from "./display-history.js";
|
|
15
15
|
import { createMarkdownSyntaxStyle, createSubtleMarkdownSyntaxStyle } from "./markdown-theme.js";
|
|
16
|
+
import { markdownInlineSegments } from "./markdown-inline.js";
|
|
17
|
+
import { hashString } from "./render-signature.js";
|
|
18
|
+
import { findToolRenderer } from "./tool-renderers/registry.js";
|
|
19
|
+
import { writeToolKey } from "./tool-renderers/write.js";
|
|
20
|
+
import { formatWritePreview, isWritePreviewTool } from "./tool-renderers/write-preview.js";
|
|
16
21
|
import { getNextPermissionMode, PERMISSION_MODE_INFO } from "../permission/mode.js";
|
|
17
22
|
import { getContextBudget } from "../context/budget.js";
|
|
18
23
|
import { getLspService } from "../lsp/index.js";
|
|
@@ -55,6 +60,7 @@ const DEFAULT_THEME = {
|
|
|
55
60
|
messageAssistantText: "#eeeeee",
|
|
56
61
|
messageAssistantAccent: "#fab283",
|
|
57
62
|
messageThinkingText: "#8b949e",
|
|
63
|
+
messageThinkingContentText: "#6e7681",
|
|
58
64
|
messageThinkingBorder: "#282828",
|
|
59
65
|
toolText: "#a6acb8",
|
|
60
66
|
toolPending: "#fab283",
|
|
@@ -103,10 +109,31 @@ const QUESTION_MAX_OPTIONS = 10;
|
|
|
103
109
|
const QUESTION_MAX_CONFIRM_ROWS = 3;
|
|
104
110
|
const QUESTION_PANEL_MIN_HEIGHT = 9;
|
|
105
111
|
const HOME_LOGO = [
|
|
106
|
-
" /\\
|
|
107
|
-
"(
|
|
108
|
-
"
|
|
112
|
+
{ text: " /\\___/\\ ", tone: "primary" },
|
|
113
|
+
{ text: "( ◕ ◕ )", tone: "primary" },
|
|
114
|
+
{ text: "( ◡ ω ◡ )", tone: "warning" },
|
|
115
|
+
{ text: " (\")_(\") ", tone: "warning" },
|
|
116
|
+
{ text: "", tone: "primary" },
|
|
117
|
+
{ text: "· ◌ ○ ◯ ·", tone: "textMuted" },
|
|
118
|
+
{ text: "", tone: "primary" },
|
|
119
|
+
{ text: "██████╗ ██╗ ██╗██████╗ ██████╗ ██╗ ███████╗", tone: "primary" },
|
|
120
|
+
{ text: "██╔══██╗██║ ██║██╔══██╗██╔══██╗██║ ██╔════╝", tone: "primary" },
|
|
121
|
+
{ text: "██████╔╝██║ ██║██████╔╝██████╔╝██║ █████╗ ", tone: "warning" },
|
|
122
|
+
{ text: "██╔══██╗██║ ██║██╔══██╗██╔══██╗██║ ██╔══╝ ", tone: "warning" },
|
|
123
|
+
{ text: "██████╔╝╚██████╔╝██████╔╝██████╔╝███████╗███████╗", tone: "accent" },
|
|
124
|
+
{ text: "╚═════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝", tone: "accent" },
|
|
125
|
+
{ text: "", tone: "primary" },
|
|
126
|
+
{ text: "── your bubble coding companion ──", tone: "secondary" },
|
|
109
127
|
];
|
|
128
|
+
function homeLogoColor(tone) {
|
|
129
|
+
switch (tone) {
|
|
130
|
+
case "primary": return theme.primary;
|
|
131
|
+
case "warning": return theme.warning;
|
|
132
|
+
case "accent": return theme.accent;
|
|
133
|
+
case "secondary": return theme.secondary;
|
|
134
|
+
case "textMuted": return theme.textMuted;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
110
137
|
const HOME_TIPS = [
|
|
111
138
|
"Type @ followed by a filename to attach file context",
|
|
112
139
|
"Press Shift+Tab to cycle Build, Plan, and Bypass modes",
|
|
@@ -318,20 +345,16 @@ function OpenTuiApp(props) {
|
|
|
318
345
|
let rootBox;
|
|
319
346
|
let sidebarShell;
|
|
320
347
|
let transcriptHost;
|
|
321
|
-
const transcriptState = {
|
|
348
|
+
const transcriptState = {
|
|
349
|
+
entries: [],
|
|
350
|
+
expandedCompactions: new Set(),
|
|
351
|
+
expandedWrites: new Set(),
|
|
352
|
+
defaultWritesExpanded: false,
|
|
353
|
+
};
|
|
322
354
|
let dock;
|
|
323
355
|
let homeComposerShell;
|
|
324
356
|
let sessionComposerShell;
|
|
325
357
|
const promptScannerSyncs = new Set();
|
|
326
|
-
const thinkingSpinnerFrames = createFrames({
|
|
327
|
-
width: 4,
|
|
328
|
-
color: theme.primary,
|
|
329
|
-
style: "blocks",
|
|
330
|
-
inactiveFactor: 0.45,
|
|
331
|
-
minAlpha: 0.25,
|
|
332
|
-
});
|
|
333
|
-
let thinkingSpinnerFrameIndex = 0;
|
|
334
|
-
let thinkingSpinnerTimer;
|
|
335
358
|
let approvalRoot;
|
|
336
359
|
let approvalHeaderTitle;
|
|
337
360
|
let approvalMetaIcon;
|
|
@@ -1524,7 +1547,6 @@ function OpenTuiApp(props) {
|
|
|
1524
1547
|
for (const timer of questionSyncTimers)
|
|
1525
1548
|
clearTimeout(timer);
|
|
1526
1549
|
questionSyncTimers.clear();
|
|
1527
|
-
stopThinkingSpinner();
|
|
1528
1550
|
if (props.options.planHandlerRef)
|
|
1529
1551
|
props.options.planHandlerRef.current = undefined;
|
|
1530
1552
|
if (props.options.approvalHandlerRef)
|
|
@@ -1668,6 +1690,16 @@ function OpenTuiApp(props) {
|
|
|
1668
1690
|
event.preventDefault?.();
|
|
1669
1691
|
return true;
|
|
1670
1692
|
}
|
|
1693
|
+
if (event.ctrl && name === "t" && !picker) {
|
|
1694
|
+
toggleThinkingVisibility();
|
|
1695
|
+
event.preventDefault?.();
|
|
1696
|
+
return true;
|
|
1697
|
+
}
|
|
1698
|
+
if (event.ctrl && name === "o" && !picker) {
|
|
1699
|
+
toggleVisibleWriteBlocks();
|
|
1700
|
+
event.preventDefault?.();
|
|
1701
|
+
return true;
|
|
1702
|
+
}
|
|
1671
1703
|
if (routeModalKey(event))
|
|
1672
1704
|
return true;
|
|
1673
1705
|
if (cycleModeFromKey(event))
|
|
@@ -1688,12 +1720,12 @@ function OpenTuiApp(props) {
|
|
|
1688
1720
|
plan: pendingPlan()?.plan,
|
|
1689
1721
|
selectedOption: approvalOptionIdx(),
|
|
1690
1722
|
showThinking: showThinking(),
|
|
1691
|
-
|
|
1692
|
-
if (transcriptState.
|
|
1693
|
-
transcriptState.
|
|
1723
|
+
onToggleWrite: (key) => {
|
|
1724
|
+
if (transcriptState.expandedWrites.has(key)) {
|
|
1725
|
+
transcriptState.expandedWrites.delete(key);
|
|
1694
1726
|
}
|
|
1695
1727
|
else {
|
|
1696
|
-
transcriptState.
|
|
1728
|
+
transcriptState.expandedWrites.add(key);
|
|
1697
1729
|
}
|
|
1698
1730
|
syncSessionMessages();
|
|
1699
1731
|
},
|
|
@@ -1708,52 +1740,61 @@ function OpenTuiApp(props) {
|
|
|
1708
1740
|
},
|
|
1709
1741
|
};
|
|
1710
1742
|
}
|
|
1711
|
-
function
|
|
1712
|
-
if (!
|
|
1743
|
+
function toggleThinkingVisibility() {
|
|
1744
|
+
if (!currentTranscriptMessages(streamingDisplay).some((message) => !!message.reasoning?.trim())) {
|
|
1745
|
+
setNotice("No thinking blocks to toggle");
|
|
1713
1746
|
return;
|
|
1714
|
-
updateTranscriptHost(transcriptHost, transcriptState, messages, transcriptOptions(), props.syntaxStyle, props.subtleSyntaxStyle);
|
|
1715
|
-
syncThinkingSpinner();
|
|
1716
|
-
syncPromptSurfaces();
|
|
1717
|
-
}
|
|
1718
|
-
function renderThinkingSpinnerFrame() {
|
|
1719
|
-
const frame = thinkingSpinnerFrames[thinkingSpinnerFrameIndex % thinkingSpinnerFrames.length] ?? "";
|
|
1720
|
-
let rendered = false;
|
|
1721
|
-
for (const entry of transcriptState.entries) {
|
|
1722
|
-
const ref = entry.refs.reasoningToggleText;
|
|
1723
|
-
if (!ref || !entry.refs.reasoningStreaming)
|
|
1724
|
-
continue;
|
|
1725
|
-
ref.content = thinkingToggleLabel(entry.refs.reasoningExpanded === true, true, frame);
|
|
1726
|
-
ref.requestRender();
|
|
1727
|
-
rendered = true;
|
|
1728
1747
|
}
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
if (thinkingSpinnerTimer)
|
|
1736
|
-
clearInterval(thinkingSpinnerTimer);
|
|
1737
|
-
thinkingSpinnerTimer = undefined;
|
|
1738
|
-
thinkingSpinnerFrameIndex = 0;
|
|
1748
|
+
setShowThinking((prev) => {
|
|
1749
|
+
const next = !prev;
|
|
1750
|
+
setNotice(next ? "Thinking blocks visible" : "Thinking blocks hidden");
|
|
1751
|
+
return next;
|
|
1752
|
+
});
|
|
1753
|
+
redrawTranscript();
|
|
1739
1754
|
}
|
|
1740
|
-
function
|
|
1741
|
-
const
|
|
1742
|
-
if (!
|
|
1743
|
-
|
|
1755
|
+
function toggleVisibleWriteBlocks() {
|
|
1756
|
+
const keys = collectVisibleWriteKeys();
|
|
1757
|
+
if (!keys.length) {
|
|
1758
|
+
setNotice("No write previews to toggle");
|
|
1744
1759
|
return;
|
|
1745
1760
|
}
|
|
1746
|
-
|
|
1747
|
-
|
|
1761
|
+
const shouldExpand = keys.some((key) => !transcriptState.expandedWrites.has(key));
|
|
1762
|
+
transcriptState.defaultWritesExpanded = shouldExpand;
|
|
1763
|
+
for (const key of keys) {
|
|
1764
|
+
if (shouldExpand)
|
|
1765
|
+
transcriptState.expandedWrites.add(key);
|
|
1766
|
+
else
|
|
1767
|
+
transcriptState.expandedWrites.delete(key);
|
|
1768
|
+
}
|
|
1769
|
+
setNotice(shouldExpand ? "Write previews expanded" : "Write previews collapsed");
|
|
1770
|
+
syncSessionMessages();
|
|
1771
|
+
}
|
|
1772
|
+
function collectVisibleWriteKeys() {
|
|
1773
|
+
const messages = currentTranscriptMessages(streamingDisplay)
|
|
1774
|
+
.filter((message) => hasRenderableMessage(message, showThinking()));
|
|
1775
|
+
const keys = [];
|
|
1776
|
+
for (const [index, message] of messages.entries()) {
|
|
1777
|
+
const messageKey = transcriptMessageKey(message, index);
|
|
1778
|
+
for (const tool of message.toolCalls ?? []) {
|
|
1779
|
+
if (isWritePreviewTool(tool)) {
|
|
1780
|
+
keys.push(writeToolKey(messageKey, tool));
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
return keys;
|
|
1785
|
+
}
|
|
1786
|
+
function syncSessionMessages(messages = currentTranscriptMessages(streamingDisplay)) {
|
|
1787
|
+
if (!transcriptHost)
|
|
1748
1788
|
return;
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
renderThinkingSpinnerFrame();
|
|
1752
|
-
}, PROMPT_SCANNER_INTERVAL_MS);
|
|
1789
|
+
updateTranscriptHost(transcriptHost, transcriptState, messages, transcriptOptions(), props.syntaxStyle, props.subtleSyntaxStyle);
|
|
1790
|
+
syncPromptSurfaces();
|
|
1753
1791
|
}
|
|
1754
1792
|
function redrawTranscript(extra, baseMessages = displayMessages) {
|
|
1755
|
-
const shouldFollow = shouldFollowTranscriptBeforeUpdate();
|
|
1756
1793
|
streamingDisplay = extra;
|
|
1794
|
+
renderTranscriptNow(streamingDisplay, baseMessages);
|
|
1795
|
+
}
|
|
1796
|
+
function renderTranscriptNow(extra, baseMessages = displayMessages) {
|
|
1797
|
+
const shouldFollow = shouldFollowTranscriptBeforeUpdate();
|
|
1757
1798
|
const nextMessages = compactDisplayMessages(extra ? [...baseMessages, extra] : baseMessages);
|
|
1758
1799
|
syncSessionMessages(nextMessages);
|
|
1759
1800
|
rootBox?.requestRender();
|
|
@@ -3078,12 +3119,7 @@ function OpenTuiApp(props) {
|
|
|
3078
3119
|
}
|
|
3079
3120
|
async function executeSlash(input) {
|
|
3080
3121
|
if (/^\/(?:thinking|toggle-thinking)(?:\s|$)/.test(input.trim())) {
|
|
3081
|
-
|
|
3082
|
-
const next = !prev;
|
|
3083
|
-
setNotice(next ? "Thinking blocks visible" : "Thinking blocks hidden");
|
|
3084
|
-
return next;
|
|
3085
|
-
});
|
|
3086
|
-
redrawTranscript();
|
|
3122
|
+
toggleThinkingVisibility();
|
|
3087
3123
|
return true;
|
|
3088
3124
|
}
|
|
3089
3125
|
const wasHomeSurfaceActive = isHomeSurfaceActive();
|
|
@@ -3525,6 +3561,8 @@ function OpenTuiApp(props) {
|
|
|
3525
3561
|
let assistantContent = "";
|
|
3526
3562
|
let assistantReasoning = "";
|
|
3527
3563
|
const toolCalls = [];
|
|
3564
|
+
let currentTurnHasToolCall = false;
|
|
3565
|
+
let turnStartedAt;
|
|
3528
3566
|
let runError;
|
|
3529
3567
|
let runCancelled = false;
|
|
3530
3568
|
try {
|
|
@@ -3533,46 +3571,72 @@ function OpenTuiApp(props) {
|
|
|
3533
3571
|
assistantContent = "";
|
|
3534
3572
|
assistantReasoning = "";
|
|
3535
3573
|
toolCalls.length = 0;
|
|
3574
|
+
currentTurnHasToolCall = false;
|
|
3575
|
+
turnStartedAt = Date.now();
|
|
3536
3576
|
redrawTranscript({
|
|
3537
3577
|
role: "assistant",
|
|
3538
3578
|
content: "",
|
|
3539
3579
|
status: "thinking",
|
|
3540
3580
|
streaming: true,
|
|
3581
|
+
turnStartedAt,
|
|
3541
3582
|
});
|
|
3542
3583
|
}
|
|
3543
3584
|
else if (event.type === "text_delta") {
|
|
3544
3585
|
assistantContent += event.content;
|
|
3586
|
+
}
|
|
3587
|
+
else if (event.type === "reasoning_delta") {
|
|
3588
|
+
assistantReasoning += event.content;
|
|
3545
3589
|
redrawTranscript({
|
|
3546
3590
|
role: "assistant",
|
|
3547
|
-
content:
|
|
3591
|
+
content: "",
|
|
3548
3592
|
reasoning: assistantReasoning || undefined,
|
|
3549
3593
|
toolCalls: toolCalls.length ? [...toolCalls] : undefined,
|
|
3550
|
-
status: "
|
|
3594
|
+
status: "thinking",
|
|
3551
3595
|
streaming: true,
|
|
3596
|
+
turnStartedAt,
|
|
3552
3597
|
});
|
|
3553
3598
|
}
|
|
3554
|
-
else if (event.type === "
|
|
3555
|
-
|
|
3599
|
+
else if (event.type === "tool_call_start") {
|
|
3600
|
+
currentTurnHasToolCall = true;
|
|
3556
3601
|
redrawTranscript({
|
|
3557
3602
|
role: "assistant",
|
|
3558
|
-
content:
|
|
3603
|
+
content: "",
|
|
3559
3604
|
reasoning: assistantReasoning || undefined,
|
|
3560
3605
|
toolCalls: toolCalls.length ? [...toolCalls] : undefined,
|
|
3561
|
-
status: "thinking",
|
|
3606
|
+
status: toolCalls.length ? undefined : "thinking",
|
|
3562
3607
|
streaming: true,
|
|
3608
|
+
turnStartedAt,
|
|
3563
3609
|
});
|
|
3564
3610
|
}
|
|
3611
|
+
else if (event.type === "tool_call_delta") {
|
|
3612
|
+
currentTurnHasToolCall = true;
|
|
3613
|
+
}
|
|
3614
|
+
else if (event.type === "tool_call_end") {
|
|
3615
|
+
currentTurnHasToolCall = true;
|
|
3616
|
+
}
|
|
3565
3617
|
else if (event.type === "tool_start") {
|
|
3566
|
-
|
|
3618
|
+
currentTurnHasToolCall = true;
|
|
3619
|
+
const now = Date.now();
|
|
3620
|
+
const existing = toolCalls.find((item) => item.id === event.id);
|
|
3621
|
+
if (existing) {
|
|
3622
|
+
existing.args = event.args;
|
|
3623
|
+
existing.streamingArgs = false;
|
|
3624
|
+
existing.status = "running";
|
|
3625
|
+
existing.startedAt = existing.startedAt ?? now;
|
|
3626
|
+
}
|
|
3627
|
+
else {
|
|
3628
|
+
toolCalls.push({ id: event.id, name: event.name, args: event.args, status: "running", startedAt: now });
|
|
3629
|
+
}
|
|
3567
3630
|
if (event.name === "question") {
|
|
3568
3631
|
scheduleQuestionSync();
|
|
3569
3632
|
}
|
|
3570
3633
|
redrawTranscript({
|
|
3571
3634
|
role: "assistant",
|
|
3572
|
-
content:
|
|
3635
|
+
content: "",
|
|
3573
3636
|
reasoning: assistantReasoning || undefined,
|
|
3574
3637
|
toolCalls: [...toolCalls],
|
|
3575
3638
|
streaming: true,
|
|
3639
|
+
turnStartedAt,
|
|
3576
3640
|
});
|
|
3577
3641
|
}
|
|
3578
3642
|
else if (event.type === "tool_end") {
|
|
@@ -3582,12 +3646,14 @@ function OpenTuiApp(props) {
|
|
|
3582
3646
|
call.isError = event.result.isError;
|
|
3583
3647
|
call.metadata = event.result.metadata;
|
|
3584
3648
|
call.status = event.result.isError ? "error" : "completed";
|
|
3649
|
+
call.completedAt = Date.now();
|
|
3585
3650
|
redrawTranscript({
|
|
3586
3651
|
role: "assistant",
|
|
3587
|
-
content: assistantContent,
|
|
3652
|
+
content: currentTurnHasToolCall ? "" : assistantContent,
|
|
3588
3653
|
reasoning: assistantReasoning || undefined,
|
|
3589
3654
|
toolCalls: [...toolCalls],
|
|
3590
3655
|
streaming: true,
|
|
3656
|
+
turnStartedAt,
|
|
3591
3657
|
});
|
|
3592
3658
|
}
|
|
3593
3659
|
if (event.name === "question") {
|
|
@@ -3596,6 +3662,30 @@ function OpenTuiApp(props) {
|
|
|
3596
3662
|
refreshGitSidebar();
|
|
3597
3663
|
syncSidebarLsp();
|
|
3598
3664
|
}
|
|
3665
|
+
else if (event.type === "tool_update") {
|
|
3666
|
+
const call = toolCalls.find((item) => item.id === event.id);
|
|
3667
|
+
if (call) {
|
|
3668
|
+
call.metadata = mergeToolMetadata(call.metadata, event.update.metadata);
|
|
3669
|
+
call.result = event.update.message ?? call.result;
|
|
3670
|
+
const finished = event.update.status === "failed" || event.update.status === "blocked" || event.update.status === "cancelled" || event.update.status === "completed";
|
|
3671
|
+
call.status = event.update.status === "failed" || event.update.status === "blocked" || event.update.status === "cancelled"
|
|
3672
|
+
? "error"
|
|
3673
|
+
: event.update.status === "completed"
|
|
3674
|
+
? "completed"
|
|
3675
|
+
: "running";
|
|
3676
|
+
call.isError = call.status === "error";
|
|
3677
|
+
if (finished && call.completedAt === undefined)
|
|
3678
|
+
call.completedAt = Date.now();
|
|
3679
|
+
redrawTranscript({
|
|
3680
|
+
role: "assistant",
|
|
3681
|
+
content: currentTurnHasToolCall ? "" : assistantContent,
|
|
3682
|
+
reasoning: assistantReasoning || undefined,
|
|
3683
|
+
toolCalls: [...toolCalls],
|
|
3684
|
+
streaming: true,
|
|
3685
|
+
turnStartedAt,
|
|
3686
|
+
});
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3599
3689
|
else if (event.type === "todos_updated") {
|
|
3600
3690
|
setTodos(event.todos);
|
|
3601
3691
|
syncSidebarTodos(event.todos);
|
|
@@ -3622,9 +3712,12 @@ function OpenTuiApp(props) {
|
|
|
3622
3712
|
bumpSidebar();
|
|
3623
3713
|
const assistantMessage = {
|
|
3624
3714
|
role: "assistant",
|
|
3625
|
-
content: assistantContent,
|
|
3715
|
+
content: currentTurnHasToolCall ? "" : assistantContent,
|
|
3626
3716
|
reasoning: assistantReasoning || undefined,
|
|
3627
3717
|
toolCalls: toolCalls.length ? [...toolCalls] : undefined,
|
|
3718
|
+
turnStartedAt,
|
|
3719
|
+
turnCompletedAt: Date.now(),
|
|
3720
|
+
turnUsage: event.usage,
|
|
3628
3721
|
};
|
|
3629
3722
|
const nextMessages = hasRenderableMessage(assistantMessage)
|
|
3630
3723
|
? [...displayMessages, assistantMessage]
|
|
@@ -3634,6 +3727,7 @@ function OpenTuiApp(props) {
|
|
|
3634
3727
|
assistantContent = "";
|
|
3635
3728
|
assistantReasoning = "";
|
|
3636
3729
|
toolCalls.length = 0;
|
|
3730
|
+
turnStartedAt = undefined;
|
|
3637
3731
|
streamingDisplay = undefined;
|
|
3638
3732
|
}
|
|
3639
3733
|
}
|
|
@@ -3736,7 +3830,7 @@ function OpenTuiApp(props) {
|
|
|
3736
3830
|
paddingLeft: 2,
|
|
3737
3831
|
paddingRight: 2,
|
|
3738
3832
|
}, [
|
|
3739
|
-
h("box", { flexShrink: 0, flexDirection: "column" }, ...HOME_LOGO.map((line) => h("text", { fg:
|
|
3833
|
+
h("box", { flexShrink: 0, flexDirection: "column", alignItems: "center" }, ...HOME_LOGO.map((line) => h("text", { fg: homeLogoColor(line.tone) }, line.text || " "))),
|
|
3740
3834
|
h("box", { height: 1, minHeight: 0, flexShrink: 1 }),
|
|
3741
3835
|
h("box", {
|
|
3742
3836
|
ref: (ref) => {
|
|
@@ -4481,7 +4575,6 @@ function OpenTuiApp(props) {
|
|
|
4481
4575
|
if (isNewHost)
|
|
4482
4576
|
transcriptState.entries = [];
|
|
4483
4577
|
updateTranscriptHost(ref, transcriptState, currentTranscriptMessages(streamingDisplay), transcriptOptions(), props.syntaxStyle, props.subtleSyntaxStyle);
|
|
4484
|
-
syncThinkingSpinner();
|
|
4485
4578
|
syncPromptSurfaces(isNewHost);
|
|
4486
4579
|
if (isNewHost)
|
|
4487
4580
|
scheduleTranscriptScrollAfterUpdate(transcriptScrollFollowing, 0);
|
|
@@ -4820,19 +4913,31 @@ function renderAssistantMessage(message, syntaxStyle, subtleSyntaxStyle, showThi
|
|
|
4820
4913
|
borderColor: theme.messageThinkingBorder,
|
|
4821
4914
|
flexDirection: "column",
|
|
4822
4915
|
flexShrink: 0,
|
|
4823
|
-
}, renderMarkdownContent(formatThinkingMarkdown(visibleReasoning), subtleSyntaxStyle, {
|
|
4916
|
+
}, h("text", { content: thinkingLabelContent(message.streaming === true, reasoningElapsedMs(message)), fg: theme.messageThinkingText, wrapMode: "none" }), renderMarkdownContent(formatThinkingMarkdown(visibleReasoning), subtleSyntaxStyle, {
|
|
4824
4917
|
streaming: message.streaming === true,
|
|
4825
|
-
fg: theme.
|
|
4918
|
+
fg: theme.messageThinkingContentText,
|
|
4826
4919
|
})));
|
|
4827
4920
|
}
|
|
4828
|
-
|
|
4921
|
+
const toolCalls = message.toolCalls ?? [];
|
|
4922
|
+
for (const tool of toolCalls)
|
|
4829
4923
|
children.push(renderTool(tool, syntaxStyle, width));
|
|
4830
|
-
|
|
4831
|
-
|
|
4924
|
+
const trimmedContent = message.content.trim();
|
|
4925
|
+
if (trimmedContent && toolCalls.length > 0) {
|
|
4926
|
+
children.push(h("box", { paddingLeft: 3, marginTop: 1, flexShrink: 0 }, h("text", { content: answerDividerStyledText(), wrapMode: "none" })));
|
|
4927
|
+
}
|
|
4928
|
+
if (trimmedContent) {
|
|
4929
|
+
children.push(h("box", { paddingLeft: 3, marginTop: 1, flexDirection: "column", flexShrink: 0 }, renderMarkdownContent(trimmedContent, syntaxStyle, {
|
|
4832
4930
|
streaming: message.streaming === true,
|
|
4833
4931
|
fg: theme.messageAssistantText,
|
|
4834
4932
|
})));
|
|
4835
4933
|
}
|
|
4934
|
+
if (message.streaming === true && trimmedContent) {
|
|
4935
|
+
children.push(h("box", { paddingLeft: 3, flexShrink: 0 }, h("text", { fg: theme.primary, wrapMode: "none" }, "▌")));
|
|
4936
|
+
}
|
|
4937
|
+
const summaryString = formatTurnSummary(message);
|
|
4938
|
+
if (summaryString) {
|
|
4939
|
+
children.push(h("box", { paddingLeft: 3, marginTop: 1, flexShrink: 0 }, h("text", { fg: theme.textMuted, wrapMode: "none" }, summaryString)));
|
|
4940
|
+
}
|
|
4836
4941
|
if (!children.length)
|
|
4837
4942
|
return null;
|
|
4838
4943
|
return h("box", { flexDirection: "column", flexShrink: 0 }, children);
|
|
@@ -4911,12 +5016,23 @@ function updateTranscriptHost(host, state, messages, options, syntaxStyle, subtl
|
|
|
4911
5016
|
}
|
|
4912
5017
|
for (const [index, message] of visibleMessages.entries()) {
|
|
4913
5018
|
const key = transcriptMessageKey(message, index);
|
|
4914
|
-
|
|
5019
|
+
if (state.defaultWritesExpanded) {
|
|
5020
|
+
for (const tool of message.toolCalls ?? []) {
|
|
5021
|
+
if (isWritePreviewTool(tool)) {
|
|
5022
|
+
state.expandedWrites.add(writeToolKey(key, tool));
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
5025
|
+
}
|
|
4915
5026
|
const compactionExpanded = state.expandedCompactions.has(key);
|
|
4916
|
-
const signature = transcriptMessageSignature(message,
|
|
5027
|
+
const signature = transcriptMessageSignature(message, compactionExpanded);
|
|
4917
5028
|
const previous = state.entries[index];
|
|
4918
5029
|
if (previous?.key === key && previous.signature === signature) {
|
|
4919
|
-
updateMessageEntry(previous, message, showThinking,
|
|
5030
|
+
updateMessageEntry(previous, message, showThinking, compactionExpanded, {
|
|
5031
|
+
syntaxStyle,
|
|
5032
|
+
expandedWrites: state.expandedWrites,
|
|
5033
|
+
width: options?.width ?? 80,
|
|
5034
|
+
onToggleWrite: options?.onToggleWrite,
|
|
5035
|
+
});
|
|
4920
5036
|
nextEntries.push(previous);
|
|
4921
5037
|
continue;
|
|
4922
5038
|
}
|
|
@@ -4924,7 +5040,7 @@ function updateTranscriptHost(host, state, messages, options, syntaxStyle, subtl
|
|
|
4924
5040
|
host.remove(previous.node.id);
|
|
4925
5041
|
previous.node.destroyRecursively();
|
|
4926
5042
|
}
|
|
4927
|
-
const entry = createMessageEntry(ctx, message, index, syntaxStyle, subtleSyntaxStyle, key, signature, showThinking, options?.width ?? 80,
|
|
5043
|
+
const entry = createMessageEntry(ctx, message, index, syntaxStyle, subtleSyntaxStyle, key, signature, showThinking, options?.width ?? 80, compactionExpanded, state.expandedWrites, options?.onToggleCompaction, options?.onToggleWrite);
|
|
4928
5044
|
if (entry) {
|
|
4929
5045
|
host.add(entry.node, index);
|
|
4930
5046
|
nextEntries.push(entry);
|
|
@@ -4967,34 +5083,23 @@ function clearTranscriptEntries(host, state) {
|
|
|
4967
5083
|
function transcriptMessageKey(message, index) {
|
|
4968
5084
|
return `${index}:${message.role}`;
|
|
4969
5085
|
}
|
|
4970
|
-
function transcriptMessageSignature(message,
|
|
5086
|
+
function transcriptMessageSignature(message, compactionExpanded = false) {
|
|
4971
5087
|
if (message.role !== "assistant")
|
|
4972
5088
|
return message.role;
|
|
4973
5089
|
if (message.syntheticKind === "ui_compact_card") {
|
|
4974
5090
|
return `compaction:${compactionExpanded ? "expanded" : "collapsed"}:${message.compactionMeta?.turns ?? 0}`;
|
|
4975
5091
|
}
|
|
4976
5092
|
const modelSwitch = parseModelSwitchMessage(message.content);
|
|
4977
|
-
const
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
5093
|
+
const pureModelSwitch = modelSwitch && !message.reasoning?.trim() && !(message.toolCalls?.length);
|
|
5094
|
+
if (pureModelSwitch) {
|
|
5095
|
+
return `assistant:model-switch:${hashString(modelSwitch)}`;
|
|
5096
|
+
}
|
|
4981
5097
|
return [
|
|
4982
5098
|
message.role,
|
|
4983
|
-
|
|
4984
|
-
message.status ?? "idle",
|
|
4985
|
-
visibleReasoning ? (thinkingExpanded ? "reasoning-expanded" : "reasoning-collapsed") : "no-reasoning",
|
|
4986
|
-
message.content.trim() ? "content" : "no-content",
|
|
4987
|
-
tools,
|
|
5099
|
+
"standard",
|
|
4988
5100
|
].join(":");
|
|
4989
5101
|
}
|
|
4990
|
-
function
|
|
4991
|
-
let hash = 5381;
|
|
4992
|
-
for (let index = 0; index < value.length; index++) {
|
|
4993
|
-
hash = ((hash << 5) + hash) ^ value.charCodeAt(index);
|
|
4994
|
-
}
|
|
4995
|
-
return (hash >>> 0).toString(36);
|
|
4996
|
-
}
|
|
4997
|
-
function updateMessageEntry(entry, message, showThinking = true, thinkingExpanded = false, compactionExpanded = false) {
|
|
5102
|
+
function updateMessageEntry(entry, message, showThinking = true, compactionExpanded = false, assistantOptions) {
|
|
4998
5103
|
if (message.role === "user") {
|
|
4999
5104
|
if (entry.refs.userText)
|
|
5000
5105
|
entry.refs.userText.content = message.content || " ";
|
|
@@ -5018,21 +5123,150 @@ function updateMessageEntry(entry, message, showThinking = true, thinkingExpande
|
|
|
5018
5123
|
}
|
|
5019
5124
|
return;
|
|
5020
5125
|
}
|
|
5126
|
+
if (assistantOptions) {
|
|
5127
|
+
updateAssistantEntry(entry, message, showThinking, assistantOptions);
|
|
5128
|
+
return;
|
|
5129
|
+
}
|
|
5130
|
+
}
|
|
5131
|
+
function updateAssistantEntry(entry, message, showThinking, options) {
|
|
5132
|
+
const content = message.content.trim();
|
|
5133
|
+
const visibleReasoning = showThinking ? message.reasoning?.trim() ?? "" : "";
|
|
5134
|
+
const tools = message.toolCalls ?? [];
|
|
5135
|
+
const showStatus = !!message.status && !visibleReasoning && !content && tools.length === 0;
|
|
5021
5136
|
if (entry.refs.statusText) {
|
|
5022
|
-
entry.refs.statusText.content = assistantStatusLabel(message);
|
|
5137
|
+
entry.refs.statusText.content = showStatus ? assistantStatusLabel(message) : "";
|
|
5138
|
+
}
|
|
5139
|
+
if (entry.refs.statusBox) {
|
|
5140
|
+
entry.refs.statusBox.visible = showStatus;
|
|
5023
5141
|
}
|
|
5024
5142
|
if (entry.refs.reasoningToggleText) {
|
|
5025
|
-
entry.refs.reasoningExpanded = thinkingExpanded;
|
|
5026
5143
|
entry.refs.reasoningStreaming = message.streaming === true;
|
|
5027
|
-
entry.refs.reasoningToggleText.content =
|
|
5144
|
+
entry.refs.reasoningToggleText.content = visibleReasoning
|
|
5145
|
+
? thinkingLabelContent(message.streaming === true, reasoningElapsedMs(message))
|
|
5146
|
+
: new StyledText([fg(theme.messageThinkingText)("")]);
|
|
5028
5147
|
}
|
|
5029
5148
|
if (entry.refs.reasoningMarkdown) {
|
|
5030
|
-
entry.refs.reasoningMarkdown
|
|
5031
|
-
|
|
5149
|
+
syncMarkdownRenderable(entry.refs.reasoningMarkdown, formatThinkingMarkdown(visibleReasoning), message.streaming === true);
|
|
5150
|
+
}
|
|
5151
|
+
if (entry.refs.reasoningBox) {
|
|
5152
|
+
entry.refs.reasoningBox.visible = !!visibleReasoning;
|
|
5153
|
+
}
|
|
5154
|
+
updateAssistantToolEntries(entry, tools, options);
|
|
5155
|
+
if (entry.refs.answerDividerBox) {
|
|
5156
|
+
const showDivider = tools.length > 0 && !!content;
|
|
5157
|
+
entry.refs.answerDividerBox.visible = showDivider;
|
|
5158
|
+
if (entry.refs.answerDividerText) {
|
|
5159
|
+
entry.refs.answerDividerText.content = showDivider
|
|
5160
|
+
? answerDividerStyledText()
|
|
5161
|
+
: new StyledText([fg(theme.textMuted)("")]);
|
|
5162
|
+
}
|
|
5032
5163
|
}
|
|
5033
5164
|
if (entry.refs.contentMarkdown) {
|
|
5034
|
-
entry.refs.contentMarkdown
|
|
5035
|
-
|
|
5165
|
+
syncMarkdownRenderable(entry.refs.contentMarkdown, content, message.streaming === true);
|
|
5166
|
+
}
|
|
5167
|
+
if (entry.refs.contentBox) {
|
|
5168
|
+
entry.refs.contentBox.visible = !!content;
|
|
5169
|
+
}
|
|
5170
|
+
if (entry.refs.contentCursorBox) {
|
|
5171
|
+
const cursorActive = message.streaming === true && !!content;
|
|
5172
|
+
entry.refs.contentCursorBox.visible = cursorActive;
|
|
5173
|
+
if (entry.refs.contentCursorText)
|
|
5174
|
+
entry.refs.contentCursorText.content = cursorActive ? "▌" : "";
|
|
5175
|
+
}
|
|
5176
|
+
const summaryString = formatTurnSummary(message);
|
|
5177
|
+
if (entry.refs.turnSummaryText) {
|
|
5178
|
+
entry.refs.turnSummaryText.content = summaryString ?? "";
|
|
5179
|
+
}
|
|
5180
|
+
if (entry.refs.turnSummaryBox) {
|
|
5181
|
+
entry.refs.turnSummaryBox.visible = !!summaryString;
|
|
5182
|
+
}
|
|
5183
|
+
}
|
|
5184
|
+
function syncMarkdownRenderable(markdown, content, streaming) {
|
|
5185
|
+
if (markdown.content === content && markdown.streaming === streaming)
|
|
5186
|
+
return;
|
|
5187
|
+
markdown.content = content;
|
|
5188
|
+
markdown.streaming = streaming;
|
|
5189
|
+
markdown.clearCache();
|
|
5190
|
+
}
|
|
5191
|
+
function updateAssistantToolEntries(entry, tools, options) {
|
|
5192
|
+
const toolsBox = entry.refs.toolsBox;
|
|
5193
|
+
if (!toolsBox)
|
|
5194
|
+
return;
|
|
5195
|
+
toolsBox.visible = tools.length > 0;
|
|
5196
|
+
const previousEntries = entry.refs.toolEntries ?? new Map();
|
|
5197
|
+
const nextEntries = new Map();
|
|
5198
|
+
tools.forEach((tool, index) => {
|
|
5199
|
+
const toolKey = writeToolKey(entry.key, tool);
|
|
5200
|
+
const writeExpanded = options.expandedWrites.has(toolKey);
|
|
5201
|
+
const signature = toolRenderableSignature(tool, writeExpanded);
|
|
5202
|
+
const previous = previousEntries.get(tool.id);
|
|
5203
|
+
if (previous?.signature === signature) {
|
|
5204
|
+
nextEntries.set(tool.id, previous);
|
|
5205
|
+
return;
|
|
5206
|
+
}
|
|
5207
|
+
if (previous) {
|
|
5208
|
+
toolsBox.remove(previous.node.id);
|
|
5209
|
+
previous.node.destroyRecursively();
|
|
5210
|
+
}
|
|
5211
|
+
const node = createToolRenderable(toolsBox.ctx, tool, options.syntaxStyle, options.width, writeExpanded, isWritePreviewTool(tool) ? () => options.onToggleWrite?.(toolKey) : undefined);
|
|
5212
|
+
toolsBox.add(node, index);
|
|
5213
|
+
nextEntries.set(tool.id, { signature, node });
|
|
5214
|
+
});
|
|
5215
|
+
for (const [id, previous] of previousEntries.entries()) {
|
|
5216
|
+
if (nextEntries.has(id))
|
|
5217
|
+
continue;
|
|
5218
|
+
toolsBox.remove(previous.node.id);
|
|
5219
|
+
previous.node.destroyRecursively();
|
|
5220
|
+
}
|
|
5221
|
+
entry.refs.toolEntries = nextEntries;
|
|
5222
|
+
}
|
|
5223
|
+
function toolRenderableSignature(tool, writeExpanded) {
|
|
5224
|
+
return [
|
|
5225
|
+
tool.id,
|
|
5226
|
+
tool.name,
|
|
5227
|
+
tool.status ?? (tool.result === undefined ? "pending" : "completed"),
|
|
5228
|
+
tool.isError ? "error" : "ok",
|
|
5229
|
+
tool.streamingArgs ? "streaming-args" : "args-complete",
|
|
5230
|
+
writeExpanded ? "expanded" : "collapsed",
|
|
5231
|
+
hashString(stableStringify(tool.args)),
|
|
5232
|
+
hashString(tool.rawArguments ?? ""),
|
|
5233
|
+
hashString(tool.result ?? ""),
|
|
5234
|
+
hashString(stableStringify(tool.metadata ?? null)),
|
|
5235
|
+
].join(":");
|
|
5236
|
+
}
|
|
5237
|
+
function mergeToolMetadata(current, incoming) {
|
|
5238
|
+
if (!incoming)
|
|
5239
|
+
return current;
|
|
5240
|
+
if (current?.kind !== "subagent" || incoming.kind !== "subagent") {
|
|
5241
|
+
return incoming;
|
|
5242
|
+
}
|
|
5243
|
+
const currentSubagents = Array.isArray(current.subagents) ? current.subagents : [];
|
|
5244
|
+
const incomingSubagents = Array.isArray(incoming.subagents) ? incoming.subagents : [];
|
|
5245
|
+
const byId = new Map();
|
|
5246
|
+
for (const item of currentSubagents) {
|
|
5247
|
+
const subAgentId = typeof item === "object" && item !== null && "subAgentId" in item
|
|
5248
|
+
? String(item.subAgentId)
|
|
5249
|
+
: "";
|
|
5250
|
+
byId.set(subAgentId || `current:${byId.size}`, item);
|
|
5251
|
+
}
|
|
5252
|
+
for (const item of incomingSubagents) {
|
|
5253
|
+
const subAgentId = typeof item === "object" && item !== null && "subAgentId" in item
|
|
5254
|
+
? String(item.subAgentId)
|
|
5255
|
+
: "";
|
|
5256
|
+
byId.set(subAgentId || `incoming:${byId.size}`, item);
|
|
5257
|
+
}
|
|
5258
|
+
return {
|
|
5259
|
+
...current,
|
|
5260
|
+
...incoming,
|
|
5261
|
+
subagents: [...byId.values()],
|
|
5262
|
+
};
|
|
5263
|
+
}
|
|
5264
|
+
function stableStringify(value) {
|
|
5265
|
+
try {
|
|
5266
|
+
return JSON.stringify(value) ?? "";
|
|
5267
|
+
}
|
|
5268
|
+
catch {
|
|
5269
|
+
return String(value);
|
|
5036
5270
|
}
|
|
5037
5271
|
}
|
|
5038
5272
|
function createBox(ctx, options, children = []) {
|
|
@@ -5056,6 +5290,7 @@ function createMarkdown(ctx, content, syntaxStyle, options) {
|
|
|
5056
5290
|
content,
|
|
5057
5291
|
syntaxStyle,
|
|
5058
5292
|
treeSitterClient,
|
|
5293
|
+
renderNode: createSemanticMarkdownRenderNode(ctx, options?.fg ?? theme.messageAssistantText),
|
|
5059
5294
|
streaming: options?.streaming === true,
|
|
5060
5295
|
conceal: true,
|
|
5061
5296
|
concealCode: false,
|
|
@@ -5075,6 +5310,87 @@ function createMarkdown(ctx, content, syntaxStyle, options) {
|
|
|
5075
5310
|
},
|
|
5076
5311
|
});
|
|
5077
5312
|
}
|
|
5313
|
+
function createSemanticMarkdownRenderNode(ctx, defaultFg) {
|
|
5314
|
+
const palette = semanticMarkdownPalette(defaultFg);
|
|
5315
|
+
return (token, context) => {
|
|
5316
|
+
switch (token?.type) {
|
|
5317
|
+
case "hr":
|
|
5318
|
+
return createText(ctx, new StyledText([
|
|
5319
|
+
fg(theme.borderSubtle)("─".repeat(48)),
|
|
5320
|
+
]), {
|
|
5321
|
+
fg: theme.borderSubtle,
|
|
5322
|
+
wrapMode: "none",
|
|
5323
|
+
flexShrink: 0,
|
|
5324
|
+
});
|
|
5325
|
+
case "heading":
|
|
5326
|
+
return createText(ctx, markdownInlineToStyledText(markdownTokenInlineTokens(token), palette, token.text ?? "", { bold: true }), {
|
|
5327
|
+
fg: defaultFg,
|
|
5328
|
+
wrapMode: "word",
|
|
5329
|
+
flexShrink: 0,
|
|
5330
|
+
});
|
|
5331
|
+
case "paragraph":
|
|
5332
|
+
return createText(ctx, markdownInlineToStyledText(markdownTokenInlineTokens(token), palette, token.text ?? ""), {
|
|
5333
|
+
fg: defaultFg,
|
|
5334
|
+
wrapMode: "word",
|
|
5335
|
+
flexShrink: 0,
|
|
5336
|
+
});
|
|
5337
|
+
case "list":
|
|
5338
|
+
return createMarkdownList(ctx, token, palette, defaultFg);
|
|
5339
|
+
default:
|
|
5340
|
+
return context.defaultRender();
|
|
5341
|
+
}
|
|
5342
|
+
};
|
|
5343
|
+
}
|
|
5344
|
+
function createMarkdownList(ctx, token, palette, defaultFg) {
|
|
5345
|
+
const ordered = token?.ordered === true;
|
|
5346
|
+
const start = typeof token?.start === "number" ? token.start : 1;
|
|
5347
|
+
const items = Array.isArray(token?.items) ? token.items : [];
|
|
5348
|
+
if (items.length === 0)
|
|
5349
|
+
return null;
|
|
5350
|
+
return createBox(ctx, {
|
|
5351
|
+
flexDirection: "column",
|
|
5352
|
+
flexShrink: 0,
|
|
5353
|
+
}, items.map((item, index) => {
|
|
5354
|
+
const marker = ordered ? `${start + index}. ` : "• ";
|
|
5355
|
+
return createText(ctx, new StyledText([
|
|
5356
|
+
fg(theme.textMuted)(marker),
|
|
5357
|
+
...markdownInlineToStyledText(markdownTokenInlineTokens(item), palette, item.text ?? "").chunks,
|
|
5358
|
+
]), {
|
|
5359
|
+
fg: defaultFg,
|
|
5360
|
+
wrapMode: "word",
|
|
5361
|
+
flexShrink: 0,
|
|
5362
|
+
});
|
|
5363
|
+
}));
|
|
5364
|
+
}
|
|
5365
|
+
function markdownTokenInlineTokens(token) {
|
|
5366
|
+
if (Array.isArray(token?.tokens))
|
|
5367
|
+
return token.tokens;
|
|
5368
|
+
if (typeof token?.text === "string")
|
|
5369
|
+
return [{ type: "text", text: token.text }];
|
|
5370
|
+
return undefined;
|
|
5371
|
+
}
|
|
5372
|
+
function semanticMarkdownPalette(defaultFg) {
|
|
5373
|
+
return {
|
|
5374
|
+
text: defaultFg,
|
|
5375
|
+
textMuted: theme.textMuted,
|
|
5376
|
+
success: theme.success,
|
|
5377
|
+
warning: theme.warning,
|
|
5378
|
+
secondary: theme.secondary,
|
|
5379
|
+
};
|
|
5380
|
+
}
|
|
5381
|
+
function markdownInlineToStyledText(tokens, palette, fallback = "", style = {}) {
|
|
5382
|
+
const chunks = markdownInlineSegments(tokens, fallback, style).map((segment) => {
|
|
5383
|
+
let chunk = fg(palette[segment.color ?? "text"])(segment.text);
|
|
5384
|
+
if (segment.bold)
|
|
5385
|
+
chunk = bold(chunk);
|
|
5386
|
+
if (segment.italic)
|
|
5387
|
+
chunk = italic(chunk);
|
|
5388
|
+
if (segment.dim)
|
|
5389
|
+
chunk = dim(chunk);
|
|
5390
|
+
return chunk;
|
|
5391
|
+
});
|
|
5392
|
+
return new StyledText(chunks);
|
|
5393
|
+
}
|
|
5078
5394
|
function createDiffRenderable(ctx, diff, filePath, syntaxStyle, width = 80) {
|
|
5079
5395
|
return new DiffRenderable(ctx, {
|
|
5080
5396
|
diff,
|
|
@@ -5137,6 +5453,24 @@ function createCodeBlockRenderable(ctx, content, filePath, syntaxStyle) {
|
|
|
5137
5453
|
lineNumbers.add(code);
|
|
5138
5454
|
return lineNumbers;
|
|
5139
5455
|
}
|
|
5456
|
+
function createToolRenderHelpers() {
|
|
5457
|
+
return {
|
|
5458
|
+
theme,
|
|
5459
|
+
createBox: (ctx, options, children) => createBox(ctx, options, children),
|
|
5460
|
+
createText: (ctx, content, options) => createText(ctx, content, (options ?? {})),
|
|
5461
|
+
createCodeBlockRenderable,
|
|
5462
|
+
createDiffRenderable,
|
|
5463
|
+
toolColor,
|
|
5464
|
+
displayToolName,
|
|
5465
|
+
toolHeader,
|
|
5466
|
+
toolPath,
|
|
5467
|
+
extractToolDiff,
|
|
5468
|
+
summarizeToolResult,
|
|
5469
|
+
isToolFinished,
|
|
5470
|
+
toolPreview,
|
|
5471
|
+
toolStateIcon,
|
|
5472
|
+
};
|
|
5473
|
+
}
|
|
5140
5474
|
function renderCodeBlockContent(content, filePath, syntaxStyle) {
|
|
5141
5475
|
return h("line_number", { fg: theme.textMuted, minWidth: 3, paddingRight: 1 }, h("code", {
|
|
5142
5476
|
content,
|
|
@@ -5148,14 +5482,14 @@ function renderCodeBlockContent(content, filePath, syntaxStyle) {
|
|
|
5148
5482
|
width: "100%",
|
|
5149
5483
|
}));
|
|
5150
5484
|
}
|
|
5151
|
-
function createMessageEntry(ctx, message, index, syntaxStyle, subtleSyntaxStyle, key, signature, showThinking = true, width = 80,
|
|
5485
|
+
function createMessageEntry(ctx, message, index, syntaxStyle, subtleSyntaxStyle, key, signature, showThinking = true, width = 80, compactionExpanded = false, expandedWrites = new Set(), onToggleCompaction, onToggleWrite) {
|
|
5152
5486
|
if (message.role === "user")
|
|
5153
5487
|
return createUserEntry(ctx, message, index, key, signature);
|
|
5154
5488
|
if (message.role === "error")
|
|
5155
5489
|
return createErrorEntry(ctx, message, key, signature);
|
|
5156
5490
|
if (message.syntheticKind === "ui_compact_card")
|
|
5157
5491
|
return createCompactionCardEntry(ctx, message, key, signature, compactionExpanded, onToggleCompaction);
|
|
5158
|
-
return createAssistantEntry(ctx, message, syntaxStyle, subtleSyntaxStyle, key, signature, showThinking, width,
|
|
5492
|
+
return createAssistantEntry(ctx, message, syntaxStyle, subtleSyntaxStyle, key, signature, showThinking, width, expandedWrites, onToggleWrite);
|
|
5159
5493
|
}
|
|
5160
5494
|
function createUserEntry(ctx, message, index, key, signature) {
|
|
5161
5495
|
const refs = {};
|
|
@@ -5200,7 +5534,7 @@ function createErrorEntry(ctx, message, key, signature) {
|
|
|
5200
5534
|
}, [text]);
|
|
5201
5535
|
return { key, signature, node, refs };
|
|
5202
5536
|
}
|
|
5203
|
-
function createAssistantEntry(ctx, message, syntaxStyle, subtleSyntaxStyle, key, signature, showThinking = true, width = 80,
|
|
5537
|
+
function createAssistantEntry(ctx, message, syntaxStyle, subtleSyntaxStyle, key, signature, showThinking = true, width = 80, expandedWrites = new Set(), onToggleWrite) {
|
|
5204
5538
|
const modelSwitch = parseModelSwitchMessage(message.content);
|
|
5205
5539
|
if (modelSwitch && !message.reasoning?.trim() && !(message.toolCalls?.length)) {
|
|
5206
5540
|
return createModelSwitchEntry(ctx, modelSwitch, key, signature);
|
|
@@ -5208,70 +5542,121 @@ function createAssistantEntry(ctx, message, syntaxStyle, subtleSyntaxStyle, key,
|
|
|
5208
5542
|
const children = [];
|
|
5209
5543
|
const refs = {};
|
|
5210
5544
|
const visibleReasoning = showThinking ? message.reasoning?.trim() : "";
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
borderColor: theme.messageThinkingBorder,
|
|
5248
|
-
flexDirection: "column",
|
|
5249
|
-
flexShrink: 0,
|
|
5250
|
-
}, reasoningChildren));
|
|
5251
|
-
}
|
|
5252
|
-
for (const tool of message.toolCalls ?? [])
|
|
5253
|
-
children.push(createToolRenderable(ctx, tool, syntaxStyle, width));
|
|
5254
|
-
if (message.content.trim()) {
|
|
5255
|
-
const markdown = createMarkdown(ctx, message.content.trim(), syntaxStyle, {
|
|
5256
|
-
streaming: message.streaming === true,
|
|
5257
|
-
fg: theme.messageAssistantText,
|
|
5258
|
-
});
|
|
5259
|
-
refs.contentMarkdown = markdown;
|
|
5260
|
-
children.push(createBox(ctx, {
|
|
5261
|
-
paddingLeft: 3,
|
|
5262
|
-
marginTop: 1,
|
|
5263
|
-
flexDirection: "column",
|
|
5545
|
+
const content = message.content.trim();
|
|
5546
|
+
const tools = message.toolCalls ?? [];
|
|
5547
|
+
const showStatus = !!message.status && !visibleReasoning && !content && tools.length === 0;
|
|
5548
|
+
const status = createText(ctx, assistantStatusLabel(message), {
|
|
5549
|
+
fg: theme.messageThinkingText,
|
|
5550
|
+
});
|
|
5551
|
+
refs.statusText = status;
|
|
5552
|
+
const statusBox = createBox(ctx, {
|
|
5553
|
+
paddingLeft: 3,
|
|
5554
|
+
marginTop: 1,
|
|
5555
|
+
flexShrink: 0,
|
|
5556
|
+
visible: showStatus,
|
|
5557
|
+
}, [status]);
|
|
5558
|
+
refs.statusBox = statusBox;
|
|
5559
|
+
children.push(statusBox);
|
|
5560
|
+
const labelText = createText(ctx, thinkingLabelContent(message.streaming === true, reasoningElapsedMs(message)), {
|
|
5561
|
+
fg: theme.messageThinkingText,
|
|
5562
|
+
wrapMode: "none",
|
|
5563
|
+
});
|
|
5564
|
+
refs.reasoningToggleText = labelText;
|
|
5565
|
+
refs.reasoningStreaming = message.streaming === true;
|
|
5566
|
+
const markdown = createMarkdown(ctx, formatThinkingMarkdown(visibleReasoning ?? ""), subtleSyntaxStyle, {
|
|
5567
|
+
streaming: message.streaming === true,
|
|
5568
|
+
fg: theme.messageThinkingContentText,
|
|
5569
|
+
});
|
|
5570
|
+
refs.reasoningMarkdown = markdown;
|
|
5571
|
+
const reasoningBox = createBox(ctx, {
|
|
5572
|
+
paddingLeft: 2,
|
|
5573
|
+
marginTop: 1,
|
|
5574
|
+
border: ["left"],
|
|
5575
|
+
borderColor: theme.messageThinkingBorder,
|
|
5576
|
+
flexDirection: "column",
|
|
5577
|
+
flexShrink: 0,
|
|
5578
|
+
visible: !!visibleReasoning,
|
|
5579
|
+
}, [
|
|
5580
|
+
createBox(ctx, {
|
|
5264
5581
|
flexShrink: 0,
|
|
5265
|
-
}, [
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5582
|
+
}, [labelText]),
|
|
5583
|
+
markdown,
|
|
5584
|
+
]);
|
|
5585
|
+
refs.reasoningBox = reasoningBox;
|
|
5586
|
+
children.push(reasoningBox);
|
|
5587
|
+
const toolsBox = createBox(ctx, {
|
|
5588
|
+
flexDirection: "column",
|
|
5589
|
+
flexShrink: 0,
|
|
5590
|
+
visible: tools.length > 0,
|
|
5591
|
+
});
|
|
5592
|
+
refs.toolsBox = toolsBox;
|
|
5593
|
+
refs.toolEntries = new Map();
|
|
5594
|
+
children.push(toolsBox);
|
|
5595
|
+
const showAnswerDivider = tools.length > 0 && !!content;
|
|
5596
|
+
const answerDividerText = createText(ctx, showAnswerDivider ? answerDividerStyledText() : new StyledText([fg(theme.textMuted)("")]), { wrapMode: "none" });
|
|
5597
|
+
refs.answerDividerText = answerDividerText;
|
|
5598
|
+
const answerDividerBox = createBox(ctx, {
|
|
5599
|
+
paddingLeft: 3,
|
|
5600
|
+
marginTop: 1,
|
|
5601
|
+
flexShrink: 0,
|
|
5602
|
+
visible: showAnswerDivider,
|
|
5603
|
+
}, [answerDividerText]);
|
|
5604
|
+
refs.answerDividerBox = answerDividerBox;
|
|
5605
|
+
children.push(answerDividerBox);
|
|
5606
|
+
const contentMarkdown = createMarkdown(ctx, content, syntaxStyle, {
|
|
5607
|
+
streaming: message.streaming === true,
|
|
5608
|
+
fg: theme.messageAssistantText,
|
|
5609
|
+
});
|
|
5610
|
+
refs.contentMarkdown = contentMarkdown;
|
|
5611
|
+
const contentBox = createBox(ctx, {
|
|
5612
|
+
paddingLeft: 3,
|
|
5613
|
+
marginTop: 1,
|
|
5614
|
+
flexDirection: "column",
|
|
5615
|
+
flexShrink: 0,
|
|
5616
|
+
visible: !!content,
|
|
5617
|
+
}, [contentMarkdown]);
|
|
5618
|
+
refs.contentBox = contentBox;
|
|
5619
|
+
children.push(contentBox);
|
|
5620
|
+
const cursorActive = message.streaming === true && !!content;
|
|
5621
|
+
const contentCursorText = createText(ctx, "▌", { fg: theme.primary, wrapMode: "none" });
|
|
5622
|
+
refs.contentCursorText = contentCursorText;
|
|
5623
|
+
const contentCursorBox = createBox(ctx, {
|
|
5624
|
+
paddingLeft: 3,
|
|
5625
|
+
flexShrink: 0,
|
|
5626
|
+
visible: cursorActive,
|
|
5627
|
+
}, [contentCursorText]);
|
|
5628
|
+
refs.contentCursorBox = contentCursorBox;
|
|
5629
|
+
children.push(contentCursorBox);
|
|
5630
|
+
const summaryString = formatTurnSummary(message);
|
|
5631
|
+
const turnSummaryText = createText(ctx, summaryString ?? "", { fg: theme.textMuted, wrapMode: "none" });
|
|
5632
|
+
refs.turnSummaryText = turnSummaryText;
|
|
5633
|
+
const turnSummaryBox = createBox(ctx, {
|
|
5634
|
+
paddingLeft: 3,
|
|
5635
|
+
marginTop: 1,
|
|
5636
|
+
flexShrink: 0,
|
|
5637
|
+
visible: !!summaryString,
|
|
5638
|
+
}, [turnSummaryText]);
|
|
5639
|
+
refs.turnSummaryBox = turnSummaryBox;
|
|
5640
|
+
children.push(turnSummaryBox);
|
|
5641
|
+
const entry = {
|
|
5270
5642
|
key,
|
|
5271
5643
|
signature,
|
|
5272
5644
|
node: createBox(ctx, { flexDirection: "column", flexShrink: 0 }, children),
|
|
5273
5645
|
refs,
|
|
5274
5646
|
};
|
|
5647
|
+
updateAssistantToolEntries(entry, tools, {
|
|
5648
|
+
syntaxStyle,
|
|
5649
|
+
expandedWrites,
|
|
5650
|
+
width,
|
|
5651
|
+
onToggleWrite,
|
|
5652
|
+
});
|
|
5653
|
+
return entry;
|
|
5654
|
+
}
|
|
5655
|
+
function answerDividerStyledText() {
|
|
5656
|
+
return new StyledText([
|
|
5657
|
+
fg(theme.accent)("◆ "),
|
|
5658
|
+
fg(theme.textMuted)(italic("Answer")),
|
|
5659
|
+
]);
|
|
5275
5660
|
}
|
|
5276
5661
|
function createCompactionCardEntry(ctx, message, key, signature, expanded, onToggle) {
|
|
5277
5662
|
const refs = {};
|
|
@@ -5389,7 +5774,7 @@ function createTodoWriteRenderable(ctx, tool) {
|
|
|
5389
5774
|
flexDirection: "column",
|
|
5390
5775
|
flexShrink: 0,
|
|
5391
5776
|
}, [
|
|
5392
|
-
createText(ctx,
|
|
5777
|
+
createText(ctx, `→ Planning tasks...`, { fg: toolColor(tool) }),
|
|
5393
5778
|
]);
|
|
5394
5779
|
}
|
|
5395
5780
|
return createBox(ctx, {
|
|
@@ -5416,90 +5801,26 @@ function createTodoWriteRenderable(ctx, tool) {
|
|
|
5416
5801
|
}),
|
|
5417
5802
|
]);
|
|
5418
5803
|
}
|
|
5419
|
-
function createToolRenderable(ctx, tool, syntaxStyle, width = 80) {
|
|
5804
|
+
function createToolRenderable(ctx, tool, syntaxStyle, width = 80, writeExpanded = false, onToggleWrite) {
|
|
5420
5805
|
if (tool.name === "question") {
|
|
5421
5806
|
return createQuestionToolRenderable(ctx, tool);
|
|
5422
5807
|
}
|
|
5423
5808
|
if (tool.name === "todo_write") {
|
|
5424
5809
|
return createTodoWriteRenderable(ctx, tool);
|
|
5425
5810
|
}
|
|
5426
|
-
const
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
}
|
|
5437
|
-
createText(ctx, new StyledText([
|
|
5438
|
-
fg(color)(`${icon} ${displayToolName(tool.name)}`),
|
|
5439
|
-
fg(theme.toolText)(header ? ` ${header}` : ""),
|
|
5440
|
-
])),
|
|
5441
|
-
createBox(ctx, {
|
|
5442
|
-
paddingLeft: 1,
|
|
5443
|
-
marginTop: 1,
|
|
5444
|
-
border: ["left"],
|
|
5445
|
-
borderColor: theme.borderSubtle,
|
|
5446
|
-
flexDirection: "column",
|
|
5447
|
-
flexShrink: 0,
|
|
5448
|
-
}, [createDiffRenderable(ctx, diff, toolPath(tool), syntaxStyle, width)]),
|
|
5449
|
-
]);
|
|
5450
|
-
}
|
|
5451
|
-
if (!tool.isError && tool.name === "write" && typeof tool.args?.content === "string" && isToolFinished(tool)) {
|
|
5452
|
-
return createBox(ctx, {
|
|
5453
|
-
paddingLeft: 3,
|
|
5454
|
-
marginTop: 1,
|
|
5455
|
-
flexDirection: "column",
|
|
5456
|
-
flexShrink: 0,
|
|
5457
|
-
}, [
|
|
5458
|
-
createText(ctx, new StyledText([
|
|
5459
|
-
fg(color)(`${icon} ${displayToolName(tool.name)}`),
|
|
5460
|
-
fg(theme.toolText)(header ? ` ${header}` : ""),
|
|
5461
|
-
])),
|
|
5462
|
-
createBox(ctx, {
|
|
5463
|
-
paddingLeft: 1,
|
|
5464
|
-
marginTop: 1,
|
|
5465
|
-
border: ["left"],
|
|
5466
|
-
borderColor: theme.borderSubtle,
|
|
5467
|
-
flexDirection: "column",
|
|
5468
|
-
flexShrink: 0,
|
|
5469
|
-
}, [createCodeBlockRenderable(ctx, tool.args.content, toolPath(tool), syntaxStyle)]),
|
|
5470
|
-
]);
|
|
5471
|
-
}
|
|
5472
|
-
const chunks = [
|
|
5473
|
-
fg(color)(`${isToolFinished(tool) ? "" : "~ "}${icon} ${displayToolName(tool.name)}`),
|
|
5474
|
-
];
|
|
5475
|
-
if (header)
|
|
5476
|
-
chunks.push(fg(theme.toolText)(` ${header}`));
|
|
5477
|
-
if (tool.result) {
|
|
5478
|
-
chunks.push(fg(theme.text)("\n"));
|
|
5479
|
-
chunks.push(fg(theme.borderSubtle)(" "));
|
|
5480
|
-
chunks.push(fg(tool.isError ? theme.toolError : theme.textMuted)(summarizeToolResult(tool)));
|
|
5481
|
-
const preview = toolPreview(tool);
|
|
5482
|
-
if (preview) {
|
|
5483
|
-
for (const line of preview.lines) {
|
|
5484
|
-
chunks.push(fg(theme.text)("\n"));
|
|
5485
|
-
chunks.push(fg(theme.borderSubtle)(" "));
|
|
5486
|
-
chunks.push(fg(theme.toolText)(line));
|
|
5487
|
-
}
|
|
5488
|
-
if (preview.omitted > 0) {
|
|
5489
|
-
chunks.push(fg(theme.text)("\n"));
|
|
5490
|
-
chunks.push(fg(theme.borderSubtle)(" "));
|
|
5491
|
-
chunks.push(fg(theme.textMuted)(`+ ${preview.omitted} more`));
|
|
5492
|
-
}
|
|
5493
|
-
}
|
|
5811
|
+
const renderer = findToolRenderer(tool);
|
|
5812
|
+
if (renderer) {
|
|
5813
|
+
return renderer.render({
|
|
5814
|
+
ctx,
|
|
5815
|
+
tool,
|
|
5816
|
+
syntaxStyle,
|
|
5817
|
+
width,
|
|
5818
|
+
writeExpanded,
|
|
5819
|
+
onToggleWrite,
|
|
5820
|
+
helpers: createToolRenderHelpers(),
|
|
5821
|
+
});
|
|
5494
5822
|
}
|
|
5495
|
-
|
|
5496
|
-
paddingLeft: 3,
|
|
5497
|
-
marginTop: 1,
|
|
5498
|
-
flexDirection: "column",
|
|
5499
|
-
flexShrink: 0,
|
|
5500
|
-
}, [
|
|
5501
|
-
createText(ctx, new StyledText(chunks), { wrapMode: "word" }),
|
|
5502
|
-
]);
|
|
5823
|
+
throw new Error(`No renderer for tool '${tool.name}'`);
|
|
5503
5824
|
}
|
|
5504
5825
|
function createQuestionToolRenderable(ctx, tool) {
|
|
5505
5826
|
const questions = questionToolQuestions(tool);
|
|
@@ -5512,7 +5833,7 @@ function createQuestionToolRenderable(ctx, tool) {
|
|
|
5512
5833
|
flexDirection: "column",
|
|
5513
5834
|
flexShrink: 0,
|
|
5514
5835
|
}, [
|
|
5515
|
-
createText(ctx,
|
|
5836
|
+
createText(ctx, `→ ${rejected ? "Asked" : "Asking"} questions...`, {
|
|
5516
5837
|
fg: rejected ? theme.textMuted : toolColor(tool),
|
|
5517
5838
|
attributes: rejected ? TextAttributes.STRIKETHROUGH : undefined,
|
|
5518
5839
|
}),
|
|
@@ -5558,16 +5879,22 @@ function renderTool(tool, syntaxStyle, width = 80) {
|
|
|
5558
5879
|
if (tool.name === "question") {
|
|
5559
5880
|
return renderQuestionTool(tool);
|
|
5560
5881
|
}
|
|
5561
|
-
const icon = tool
|
|
5882
|
+
const icon = toolStateIcon(tool);
|
|
5562
5883
|
const color = toolColor(tool);
|
|
5563
5884
|
const diff = extractToolDiff(tool);
|
|
5564
5885
|
if (diff && !tool.isError && tool.name === "edit") {
|
|
5565
5886
|
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)));
|
|
5566
5887
|
}
|
|
5567
|
-
if (
|
|
5568
|
-
|
|
5888
|
+
if (isWritePreviewTool(tool)) {
|
|
5889
|
+
const preview = formatWritePreview(tool.args.content, false);
|
|
5890
|
+
const summary = tool.result ?? `${isToolFinished(tool) ? "Prepared" : "Writing"} ${tool.args.content.split(/\r?\n/).length} lines to ${toolPath(tool) ?? "file"}`;
|
|
5891
|
+
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: 0, border: ["left"], borderColor: theme.borderSubtle, flexDirection: "column", flexShrink: 0 }, h("text", { fg: theme.textMuted }, `└ ${summary}`), renderCodeBlockContent(preview.content, toolPath(tool), syntaxStyle), preview.omittedLines > 0
|
|
5892
|
+
? h("text", { fg: theme.textMuted }, `... +${preview.omittedLines} lines (ctrl+o to expand)`)
|
|
5893
|
+
: preview.omittedChars > 0
|
|
5894
|
+
? h("text", { fg: theme.textMuted }, `... +${preview.omittedChars} chars (ctrl+o to expand)`)
|
|
5895
|
+
: null));
|
|
5569
5896
|
}
|
|
5570
|
-
return h("box", { paddingLeft: 3, marginTop: 1, flexDirection: "column", flexShrink: 0 }, h("text", { fg: color }, `${
|
|
5897
|
+
return h("box", { paddingLeft: 3, marginTop: 1, flexDirection: "column", flexShrink: 0 }, h("text", { fg: color }, `${icon} ${displayToolName(tool.name)}${toolHeader(tool) ? ` ${toolHeader(tool)}` : ""}`), () => tool.result ? h("text", { fg: tool.isError ? theme.toolError : theme.textMuted, wrapMode: "word" }, toolSummaryWithPreview(tool)) : null);
|
|
5571
5898
|
}
|
|
5572
5899
|
function renderQuestionTool(tool) {
|
|
5573
5900
|
const questions = questionToolQuestions(tool);
|
|
@@ -5577,7 +5904,7 @@ function renderQuestionTool(tool) {
|
|
|
5577
5904
|
return h("box", { paddingLeft: 3, marginTop: 1, flexDirection: "column", flexShrink: 0 }, h("text", {
|
|
5578
5905
|
fg: rejected ? theme.textMuted : toolColor(tool),
|
|
5579
5906
|
attributes: rejected ? TextAttributes.STRIKETHROUGH : undefined,
|
|
5580
|
-
},
|
|
5907
|
+
}, `→ ${rejected ? "Asked" : "Asking"} questions...`));
|
|
5581
5908
|
}
|
|
5582
5909
|
return h("box", {
|
|
5583
5910
|
border: ["left"],
|
|
@@ -5842,11 +6169,9 @@ function formatDisplayContentParts(content, labelStart) {
|
|
|
5842
6169
|
function reconstructDisplayMessages(agentMessages) {
|
|
5843
6170
|
const result = [];
|
|
5844
6171
|
for (const message of agentMessages) {
|
|
5845
|
-
if (message.role === "system" || message.role === "tool")
|
|
6172
|
+
if (message.role === "system" || message.role === "meta" || message.role === "tool")
|
|
5846
6173
|
continue;
|
|
5847
6174
|
if (message.role === "user") {
|
|
5848
|
-
if (message.isMeta)
|
|
5849
|
-
continue;
|
|
5850
6175
|
result.push({
|
|
5851
6176
|
role: "user",
|
|
5852
6177
|
content: typeof message.content === "string"
|
|
@@ -5935,7 +6260,9 @@ function formatTranscript(messages, options) {
|
|
|
5935
6260
|
if (visibleReasoning) {
|
|
5936
6261
|
appendBlank();
|
|
5937
6262
|
append("│ ", theme.messageThinkingBorder);
|
|
5938
|
-
|
|
6263
|
+
chunks.push(fg(theme.messageThinkingText)(italic("Thinking\n")));
|
|
6264
|
+
append("│ ", theme.messageThinkingBorder);
|
|
6265
|
+
appendLine(truncate(formatThinkingMarkdown(visibleReasoning), 500), theme.messageThinkingContentText);
|
|
5939
6266
|
}
|
|
5940
6267
|
if (message.status && !visibleReasoning && !message.content.trim() && !(message.toolCalls?.length)) {
|
|
5941
6268
|
appendBlank();
|
|
@@ -5946,7 +6273,7 @@ function formatTranscript(messages, options) {
|
|
|
5946
6273
|
appendBlank();
|
|
5947
6274
|
const icon = tool.name === "bash" ? "$" : tool.name === "edit" || tool.name === "write" ? "✎" : "●";
|
|
5948
6275
|
const color = toolColor(tool);
|
|
5949
|
-
append(` ${
|
|
6276
|
+
append(` ${icon} `, color);
|
|
5950
6277
|
append(displayToolName(tool.name), color);
|
|
5951
6278
|
const header = toolHeader(tool);
|
|
5952
6279
|
if (header)
|
|
@@ -5996,7 +6323,7 @@ function renderHomeState(input) {
|
|
|
5996
6323
|
flexDirection: "column",
|
|
5997
6324
|
alignItems: "center",
|
|
5998
6325
|
justifyContent: "center",
|
|
5999
|
-
}, h("box", { flexDirection: "column", flexShrink: 0, width: "100%" }, h("text", { fg: theme.text }, ""), h("text", { fg: theme.text }, ""), ...HOME_LOGO.map((line) => h("text", { fg:
|
|
6326
|
+
}, h("box", { flexDirection: "column", flexShrink: 0, width: "100%" }, h("text", { fg: theme.text }, ""), h("text", { fg: theme.text }, ""), ...HOME_LOGO.map((line) => h("text", { fg: homeLogoColor(line.tone) }, centerLine(line.text || " ", width))), h("text", { fg: theme.text }, ""), h("text", { fg: theme.warning }, centerLine(`● Tip ${input.tip}`, width)), cwd ? h("text", { fg: theme.textMuted }, centerLine(` ${cwd}`, width)) : null));
|
|
6000
6327
|
}
|
|
6001
6328
|
function hasRenderableMessage(message, showThinking = true) {
|
|
6002
6329
|
if (message.role === "error")
|
|
@@ -6172,6 +6499,11 @@ function displayToolName(name) {
|
|
|
6172
6499
|
glob: "Glob",
|
|
6173
6500
|
web_fetch: "WebFetch",
|
|
6174
6501
|
web_search: "WebSearch",
|
|
6502
|
+
subagent: "Subagent",
|
|
6503
|
+
spawn_agent: "SpawnAgent",
|
|
6504
|
+
wait_agent: "WaitAgent",
|
|
6505
|
+
send_input: "SendInput",
|
|
6506
|
+
close_agent: "CloseAgent",
|
|
6175
6507
|
task: "Task",
|
|
6176
6508
|
todo: "Todo",
|
|
6177
6509
|
question: "Questions",
|
|
@@ -6180,6 +6512,20 @@ function displayToolName(name) {
|
|
|
6180
6512
|
}
|
|
6181
6513
|
function toolHeader(tool) {
|
|
6182
6514
|
const args = tool.args || {};
|
|
6515
|
+
if (tool.name === "subagent") {
|
|
6516
|
+
if (typeof args.agent === "string")
|
|
6517
|
+
return `(${args.agent})`;
|
|
6518
|
+
if (Array.isArray(args.tasks))
|
|
6519
|
+
return `(${args.tasks.length} tasks)`;
|
|
6520
|
+
}
|
|
6521
|
+
if (tool.name === "spawn_agent") {
|
|
6522
|
+
const agent = args.agent_type ?? args.agent ?? "default";
|
|
6523
|
+
return `(${agent})`;
|
|
6524
|
+
}
|
|
6525
|
+
if (tool.name === "wait_agent" || tool.name === "send_input" || tool.name === "close_agent") {
|
|
6526
|
+
const agentId = args.agent_id ?? (Array.isArray(args.agent_ids) ? `${args.agent_ids.length} agents` : undefined);
|
|
6527
|
+
return agentId ? `(${truncate(String(agentId), 64)})` : "";
|
|
6528
|
+
}
|
|
6183
6529
|
const value = args.path ?? args.command ?? args.pattern ?? args.url ?? args.query;
|
|
6184
6530
|
return value ? `(${truncate(String(value).replace(/\n/g, " "), 64)})` : "";
|
|
6185
6531
|
}
|
|
@@ -6225,8 +6571,11 @@ function filetype(filePath) {
|
|
|
6225
6571
|
return ext ? map[ext] : undefined;
|
|
6226
6572
|
}
|
|
6227
6573
|
function summarizeToolResult(tool) {
|
|
6228
|
-
if (!isToolFinished(tool))
|
|
6229
|
-
|
|
6574
|
+
if (!isToolFinished(tool)) {
|
|
6575
|
+
if (tool.status === "running")
|
|
6576
|
+
return "running";
|
|
6577
|
+
return tool.streamingArgs ? "preparing" : "pending";
|
|
6578
|
+
}
|
|
6230
6579
|
if (tool.name === "question") {
|
|
6231
6580
|
if (isQuestionRejected(tool))
|
|
6232
6581
|
return "dismissed";
|
|
@@ -6237,14 +6586,47 @@ function summarizeToolResult(tool) {
|
|
|
6237
6586
|
if (tool.isError)
|
|
6238
6587
|
return truncate(result.split("\n").find(Boolean) || "error", 120);
|
|
6239
6588
|
const lines = result.replace(/\r\n/g, "\n").split("\n").filter((line) => line.trim()).length;
|
|
6589
|
+
const matches = typeof tool.metadata?.matches === "number" ? tool.metadata.matches : undefined;
|
|
6590
|
+
if (tool.name === "read")
|
|
6591
|
+
return "";
|
|
6240
6592
|
if (tool.name === "edit")
|
|
6241
6593
|
return "patched file";
|
|
6242
6594
|
if (tool.name === "write")
|
|
6243
6595
|
return "wrote file";
|
|
6244
|
-
if (tool.name === "
|
|
6596
|
+
if (tool.name === "grep" || tool.name === "glob") {
|
|
6597
|
+
if (matches !== undefined)
|
|
6598
|
+
return `${matches} match${matches === 1 ? "" : "es"}`;
|
|
6599
|
+
return lines ? `${lines} line${lines === 1 ? "" : "s"}` : "no matches";
|
|
6600
|
+
}
|
|
6601
|
+
if (tool.name === "bash") {
|
|
6602
|
+
if (matches !== undefined)
|
|
6603
|
+
return `${matches} match${matches === 1 ? "" : "es"}`;
|
|
6245
6604
|
return lines ? `${lines} line${lines === 1 ? "" : "s"} output` : "done";
|
|
6605
|
+
}
|
|
6246
6606
|
return lines ? `${lines} line${lines === 1 ? "" : "s"}` : "done";
|
|
6247
6607
|
}
|
|
6608
|
+
function toolStateIcon(tool) {
|
|
6609
|
+
if (tool.isError || tool.status === "error")
|
|
6610
|
+
return "✗";
|
|
6611
|
+
if (!isToolFinished(tool)) {
|
|
6612
|
+
if (tool.status === "running")
|
|
6613
|
+
return "◐";
|
|
6614
|
+
return "◌";
|
|
6615
|
+
}
|
|
6616
|
+
if (tool.name === "bash")
|
|
6617
|
+
return "$";
|
|
6618
|
+
if (tool.name === "edit")
|
|
6619
|
+
return "✎";
|
|
6620
|
+
if (tool.name === "write")
|
|
6621
|
+
return "✎";
|
|
6622
|
+
if (tool.name === "read")
|
|
6623
|
+
return "▤";
|
|
6624
|
+
if (tool.name === "grep" || tool.name === "glob")
|
|
6625
|
+
return "⌕";
|
|
6626
|
+
if (tool.name === "web_fetch" || tool.name === "web_search")
|
|
6627
|
+
return "⌖";
|
|
6628
|
+
return "●";
|
|
6629
|
+
}
|
|
6248
6630
|
function toolSummaryWithPreview(tool) {
|
|
6249
6631
|
const summary = ` ${summarizeToolResult(tool)}`;
|
|
6250
6632
|
const preview = toolPreview(tool);
|
|
@@ -6259,7 +6641,7 @@ function toolSummaryWithPreview(tool) {
|
|
|
6259
6641
|
function toolPreview(tool) {
|
|
6260
6642
|
if (!isToolFinished(tool) || tool.isError || !tool.result)
|
|
6261
6643
|
return undefined;
|
|
6262
|
-
if (tool.name !== "
|
|
6644
|
+
if (tool.name !== "glob")
|
|
6263
6645
|
return undefined;
|
|
6264
6646
|
const lines = tool.result
|
|
6265
6647
|
.replace(/\r\n/g, "\n")
|
|
@@ -6315,7 +6697,10 @@ function isToolFinished(tool) {
|
|
|
6315
6697
|
function assistantStatusLabel(message) {
|
|
6316
6698
|
if (message.status === "responding")
|
|
6317
6699
|
return "Responding...";
|
|
6318
|
-
|
|
6700
|
+
const elapsed = formatDuration(reasoningElapsedMs(message));
|
|
6701
|
+
if (message.streaming)
|
|
6702
|
+
return elapsed ? `Thinking ${elapsed}...` : "Thinking...";
|
|
6703
|
+
return elapsed ? `Thought for ${elapsed}` : "Thinking";
|
|
6319
6704
|
}
|
|
6320
6705
|
function buildContextGauge(percent, barWidth) {
|
|
6321
6706
|
const clamped = Math.max(0, Math.min(100, percent));
|
|
@@ -6346,9 +6731,69 @@ function formatContextRemaining(value) {
|
|
|
6346
6731
|
return `${(value / 1_000).toFixed(1)}K`;
|
|
6347
6732
|
return String(value);
|
|
6348
6733
|
}
|
|
6349
|
-
function
|
|
6350
|
-
const
|
|
6351
|
-
|
|
6734
|
+
function thinkingLabelContent(streaming = false, elapsedMs) {
|
|
6735
|
+
const elapsed = formatDuration(elapsedMs);
|
|
6736
|
+
const label = streaming
|
|
6737
|
+
? (elapsed ? `Thinking ${elapsed}...` : "Thinking...")
|
|
6738
|
+
: (elapsed ? `Thought for ${elapsed}` : "Thought");
|
|
6739
|
+
return new StyledText([
|
|
6740
|
+
fg(theme.messageThinkingText)(italic(label)),
|
|
6741
|
+
]);
|
|
6742
|
+
}
|
|
6743
|
+
function formatDuration(ms) {
|
|
6744
|
+
if (ms === undefined || !Number.isFinite(ms) || ms <= 0)
|
|
6745
|
+
return "";
|
|
6746
|
+
if (ms < 1000)
|
|
6747
|
+
return `${Math.max(1, Math.round(ms))}ms`;
|
|
6748
|
+
const seconds = ms / 1000;
|
|
6749
|
+
if (seconds < 10)
|
|
6750
|
+
return `${seconds.toFixed(1)}s`;
|
|
6751
|
+
if (seconds < 60)
|
|
6752
|
+
return `${Math.round(seconds)}s`;
|
|
6753
|
+
const minutes = Math.floor(seconds / 60);
|
|
6754
|
+
const remSec = Math.round(seconds - minutes * 60);
|
|
6755
|
+
return remSec === 0 ? `${minutes}m` : `${minutes}m${remSec}s`;
|
|
6756
|
+
}
|
|
6757
|
+
function reasoningElapsedMs(message) {
|
|
6758
|
+
if (message.turnStartedAt === undefined)
|
|
6759
|
+
return undefined;
|
|
6760
|
+
const end = !message.streaming ? (message.turnCompletedAt ?? Date.now()) : Date.now();
|
|
6761
|
+
const diff = end - message.turnStartedAt;
|
|
6762
|
+
return diff > 0 ? diff : undefined;
|
|
6763
|
+
}
|
|
6764
|
+
function toolElapsedMs(tool) {
|
|
6765
|
+
if (tool.startedAt === undefined)
|
|
6766
|
+
return undefined;
|
|
6767
|
+
const end = tool.completedAt ?? (tool.status === "completed" || tool.status === "error" ? Date.now() : undefined);
|
|
6768
|
+
if (end === undefined)
|
|
6769
|
+
return undefined;
|
|
6770
|
+
const diff = end - tool.startedAt;
|
|
6771
|
+
return diff > 0 ? diff : undefined;
|
|
6772
|
+
}
|
|
6773
|
+
function turnElapsedMs(message) {
|
|
6774
|
+
if (message.turnStartedAt === undefined)
|
|
6775
|
+
return undefined;
|
|
6776
|
+
const end = message.turnCompletedAt ?? Date.now();
|
|
6777
|
+
const diff = end - message.turnStartedAt;
|
|
6778
|
+
return diff > 0 ? diff : undefined;
|
|
6779
|
+
}
|
|
6780
|
+
function formatTurnSummary(message) {
|
|
6781
|
+
const parts = [];
|
|
6782
|
+
const elapsed = turnElapsedMs(message);
|
|
6783
|
+
if (elapsed !== undefined)
|
|
6784
|
+
parts.push(formatDuration(elapsed));
|
|
6785
|
+
const usage = message.turnUsage;
|
|
6786
|
+
if (usage) {
|
|
6787
|
+
if (usage.promptTokens)
|
|
6788
|
+
parts.push(`${formatCompactNumber(usage.promptTokens)}↑`);
|
|
6789
|
+
if (usage.completionTokens)
|
|
6790
|
+
parts.push(`${formatCompactNumber(usage.completionTokens)}↓`);
|
|
6791
|
+
if (usage.reasoningTokens)
|
|
6792
|
+
parts.push(`${formatCompactNumber(usage.reasoningTokens)}◇`);
|
|
6793
|
+
}
|
|
6794
|
+
if (!parts.length)
|
|
6795
|
+
return undefined;
|
|
6796
|
+
return `· ${parts.join(" · ")}`;
|
|
6352
6797
|
}
|
|
6353
6798
|
function truncate(value, max) {
|
|
6354
6799
|
return value.length > max ? value.slice(0, Math.max(1, max - 1)).trimEnd() + "…" : value;
|