@bubblebrain-ai/bubble 0.0.3 → 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/README.md +8 -3
- package/dist/agent/budget-ledger.d.ts +20 -0
- package/dist/agent/budget-ledger.js +51 -0
- package/dist/agent/execution-governor.d.ts +14 -0
- package/dist/agent/execution-governor.js +172 -14
- 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/task-classifier.d.ts +1 -1
- package/dist/agent/task-classifier.js +60 -0
- package/dist/agent/tool-intent.d.ts +14 -0
- package/dist/agent/tool-intent.js +125 -1
- package/dist/agent.d.ts +60 -1
- package/dist/agent.js +606 -53
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +45 -0
- 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.d.ts +1 -1
- package/dist/main.js +13 -6
- package/dist/mcp/manager.js +1 -0
- package/dist/orchestrator/default-hooks.js +92 -1
- package/dist/orchestrator/hooks.d.ts +10 -0
- package/dist/prompt/compose.d.ts +1 -0
- package/dist/prompt/compose.js +20 -1
- package/dist/prompt/environment.js +21 -2
- package/dist/prompt/provider-prompts/deepseek.d.ts +1 -0
- package/dist/prompt/provider-prompts/deepseek.js +8 -0
- package/dist/prompt/provider-prompts/glm.d.ts +1 -0
- package/dist/prompt/provider-prompts/glm.js +7 -0
- package/dist/prompt/provider-prompts/kimi.d.ts +1 -0
- package/dist/prompt/provider-prompts/kimi.js +7 -0
- package/dist/prompt/reminders.d.ts +5 -1
- package/dist/prompt/reminders.js +51 -6
- package/dist/prompt/runtime.d.ts +1 -1
- package/dist/prompt/runtime.js +16 -3
- package/dist/prompt/task-reminders.d.ts +2 -0
- package/dist/prompt/task-reminders.js +56 -0
- 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/slash-commands/commands.js +2 -3
- 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 +12 -7
- package/dist/tools/edit-apply.d.ts +25 -0
- package/dist/tools/edit-apply.js +197 -0
- package/dist/tools/edit.js +64 -52
- 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 +10 -1
- package/dist/tui/display-history.d.ts +8 -1
- package/dist/tui/image-paste.d.ts +41 -0
- package/dist/tui/image-paste.js +217 -0
- 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 +814 -269
- 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 +3 -3
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";
|
|
@@ -20,6 +25,7 @@ import { inferBashPrefix } from "../approval/session-cache.js";
|
|
|
20
25
|
import { createFrames } from "./opencode-spinner.js";
|
|
21
26
|
import { copyTextToClipboard } from "./clipboard.js";
|
|
22
27
|
import { readGitSidebarState } from "./sidebar-state.js";
|
|
28
|
+
import { buildImageContentPartsFromLabels, imageAttachmentLabelPattern, resolveComposerImagePaths, resolveImageInput, } from "./image-paste.js";
|
|
23
29
|
import { isModeCycleKeyEvent, isModeCycleSequence, isModifiedEnterSequence, PROMPT_TEXTAREA_KEYBINDINGS, } from "./prompt-keybindings.js";
|
|
24
30
|
import { keyNameFromEvent, keyNameFromSequence } from "./global-key-router.js";
|
|
25
31
|
const treeSitterClient = getTreeSitterClient();
|
|
@@ -54,6 +60,7 @@ const DEFAULT_THEME = {
|
|
|
54
60
|
messageAssistantText: "#eeeeee",
|
|
55
61
|
messageAssistantAccent: "#fab283",
|
|
56
62
|
messageThinkingText: "#8b949e",
|
|
63
|
+
messageThinkingContentText: "#6e7681",
|
|
57
64
|
messageThinkingBorder: "#282828",
|
|
58
65
|
toolText: "#a6acb8",
|
|
59
66
|
toolPending: "#fab283",
|
|
@@ -102,10 +109,31 @@ const QUESTION_MAX_OPTIONS = 10;
|
|
|
102
109
|
const QUESTION_MAX_CONFIRM_ROWS = 3;
|
|
103
110
|
const QUESTION_PANEL_MIN_HEIGHT = 9;
|
|
104
111
|
const HOME_LOGO = [
|
|
105
|
-
" /\\
|
|
106
|
-
"(
|
|
107
|
-
"
|
|
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" },
|
|
108
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
|
+
}
|
|
109
137
|
const HOME_TIPS = [
|
|
110
138
|
"Type @ followed by a filename to attach file context",
|
|
111
139
|
"Press Shift+Tab to cycle Build, Plan, and Bypass modes",
|
|
@@ -263,6 +291,10 @@ function OpenTuiApp(props) {
|
|
|
263
291
|
.filter((message) => message.role === "user" && message.content !== "(multimedia)")
|
|
264
292
|
.map((message) => message.content)
|
|
265
293
|
.slice(-PROMPT_HISTORY_LIMIT);
|
|
294
|
+
let nextImageAttachmentIndex = nextImageLabelIndex(displayMessages);
|
|
295
|
+
const pendingImageAttachments = new Map();
|
|
296
|
+
let composerImageResolutionSeq = 0;
|
|
297
|
+
let applyingComposerImageReplacement = false;
|
|
266
298
|
let promptHistoryIndex;
|
|
267
299
|
let promptHistoryDraft = "";
|
|
268
300
|
const [isRunning, setIsRunning] = createSignal(false);
|
|
@@ -313,20 +345,16 @@ function OpenTuiApp(props) {
|
|
|
313
345
|
let rootBox;
|
|
314
346
|
let sidebarShell;
|
|
315
347
|
let transcriptHost;
|
|
316
|
-
const transcriptState = {
|
|
348
|
+
const transcriptState = {
|
|
349
|
+
entries: [],
|
|
350
|
+
expandedCompactions: new Set(),
|
|
351
|
+
expandedWrites: new Set(),
|
|
352
|
+
defaultWritesExpanded: false,
|
|
353
|
+
};
|
|
317
354
|
let dock;
|
|
318
355
|
let homeComposerShell;
|
|
319
356
|
let sessionComposerShell;
|
|
320
357
|
const promptScannerSyncs = new Set();
|
|
321
|
-
const thinkingSpinnerFrames = createFrames({
|
|
322
|
-
width: 4,
|
|
323
|
-
color: theme.primary,
|
|
324
|
-
style: "blocks",
|
|
325
|
-
inactiveFactor: 0.45,
|
|
326
|
-
minAlpha: 0.25,
|
|
327
|
-
});
|
|
328
|
-
let thinkingSpinnerFrameIndex = 0;
|
|
329
|
-
let thinkingSpinnerTimer;
|
|
330
358
|
let approvalRoot;
|
|
331
359
|
let approvalHeaderTitle;
|
|
332
360
|
let approvalMetaIcon;
|
|
@@ -1519,7 +1547,6 @@ function OpenTuiApp(props) {
|
|
|
1519
1547
|
for (const timer of questionSyncTimers)
|
|
1520
1548
|
clearTimeout(timer);
|
|
1521
1549
|
questionSyncTimers.clear();
|
|
1522
|
-
stopThinkingSpinner();
|
|
1523
1550
|
if (props.options.planHandlerRef)
|
|
1524
1551
|
props.options.planHandlerRef.current = undefined;
|
|
1525
1552
|
if (props.options.approvalHandlerRef)
|
|
@@ -1663,6 +1690,16 @@ function OpenTuiApp(props) {
|
|
|
1663
1690
|
event.preventDefault?.();
|
|
1664
1691
|
return true;
|
|
1665
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
|
+
}
|
|
1666
1703
|
if (routeModalKey(event))
|
|
1667
1704
|
return true;
|
|
1668
1705
|
if (cycleModeFromKey(event))
|
|
@@ -1683,12 +1720,12 @@ function OpenTuiApp(props) {
|
|
|
1683
1720
|
plan: pendingPlan()?.plan,
|
|
1684
1721
|
selectedOption: approvalOptionIdx(),
|
|
1685
1722
|
showThinking: showThinking(),
|
|
1686
|
-
|
|
1687
|
-
if (transcriptState.
|
|
1688
|
-
transcriptState.
|
|
1723
|
+
onToggleWrite: (key) => {
|
|
1724
|
+
if (transcriptState.expandedWrites.has(key)) {
|
|
1725
|
+
transcriptState.expandedWrites.delete(key);
|
|
1689
1726
|
}
|
|
1690
1727
|
else {
|
|
1691
|
-
transcriptState.
|
|
1728
|
+
transcriptState.expandedWrites.add(key);
|
|
1692
1729
|
}
|
|
1693
1730
|
syncSessionMessages();
|
|
1694
1731
|
},
|
|
@@ -1703,52 +1740,61 @@ function OpenTuiApp(props) {
|
|
|
1703
1740
|
},
|
|
1704
1741
|
};
|
|
1705
1742
|
}
|
|
1706
|
-
function
|
|
1707
|
-
if (!
|
|
1743
|
+
function toggleThinkingVisibility() {
|
|
1744
|
+
if (!currentTranscriptMessages(streamingDisplay).some((message) => !!message.reasoning?.trim())) {
|
|
1745
|
+
setNotice("No thinking blocks to toggle");
|
|
1708
1746
|
return;
|
|
1709
|
-
updateTranscriptHost(transcriptHost, transcriptState, messages, transcriptOptions(), props.syntaxStyle, props.subtleSyntaxStyle);
|
|
1710
|
-
syncThinkingSpinner();
|
|
1711
|
-
syncPromptSurfaces();
|
|
1712
|
-
}
|
|
1713
|
-
function renderThinkingSpinnerFrame() {
|
|
1714
|
-
const frame = thinkingSpinnerFrames[thinkingSpinnerFrameIndex % thinkingSpinnerFrames.length] ?? "";
|
|
1715
|
-
let rendered = false;
|
|
1716
|
-
for (const entry of transcriptState.entries) {
|
|
1717
|
-
const ref = entry.refs.reasoningToggleText;
|
|
1718
|
-
if (!ref || !entry.refs.reasoningStreaming)
|
|
1719
|
-
continue;
|
|
1720
|
-
ref.content = thinkingToggleLabel(entry.refs.reasoningExpanded === true, true, frame);
|
|
1721
|
-
ref.requestRender();
|
|
1722
|
-
rendered = true;
|
|
1723
|
-
}
|
|
1724
|
-
if (rendered) {
|
|
1725
|
-
transcriptHost?.requestRender();
|
|
1726
|
-
rootBox?.requestRender();
|
|
1727
1747
|
}
|
|
1748
|
+
setShowThinking((prev) => {
|
|
1749
|
+
const next = !prev;
|
|
1750
|
+
setNotice(next ? "Thinking blocks visible" : "Thinking blocks hidden");
|
|
1751
|
+
return next;
|
|
1752
|
+
});
|
|
1753
|
+
redrawTranscript();
|
|
1728
1754
|
}
|
|
1729
|
-
function
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
thinkingSpinnerFrameIndex = 0;
|
|
1734
|
-
}
|
|
1735
|
-
function syncThinkingSpinner() {
|
|
1736
|
-
const hasStreamingThinking = transcriptState.entries.some((entry) => !!entry.refs.reasoningToggleText && entry.refs.reasoningStreaming === true);
|
|
1737
|
-
if (!hasStreamingThinking) {
|
|
1738
|
-
stopThinkingSpinner();
|
|
1755
|
+
function toggleVisibleWriteBlocks() {
|
|
1756
|
+
const keys = collectVisibleWriteKeys();
|
|
1757
|
+
if (!keys.length) {
|
|
1758
|
+
setNotice("No write previews to toggle");
|
|
1739
1759
|
return;
|
|
1740
1760
|
}
|
|
1741
|
-
|
|
1742
|
-
|
|
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)
|
|
1743
1788
|
return;
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
renderThinkingSpinnerFrame();
|
|
1747
|
-
}, PROMPT_SCANNER_INTERVAL_MS);
|
|
1789
|
+
updateTranscriptHost(transcriptHost, transcriptState, messages, transcriptOptions(), props.syntaxStyle, props.subtleSyntaxStyle);
|
|
1790
|
+
syncPromptSurfaces();
|
|
1748
1791
|
}
|
|
1749
1792
|
function redrawTranscript(extra, baseMessages = displayMessages) {
|
|
1750
|
-
const shouldFollow = shouldFollowTranscriptBeforeUpdate();
|
|
1751
1793
|
streamingDisplay = extra;
|
|
1794
|
+
renderTranscriptNow(streamingDisplay, baseMessages);
|
|
1795
|
+
}
|
|
1796
|
+
function renderTranscriptNow(extra, baseMessages = displayMessages) {
|
|
1797
|
+
const shouldFollow = shouldFollowTranscriptBeforeUpdate();
|
|
1752
1798
|
const nextMessages = compactDisplayMessages(extra ? [...baseMessages, extra] : baseMessages);
|
|
1753
1799
|
syncSessionMessages(nextMessages);
|
|
1754
1800
|
rootBox?.requestRender();
|
|
@@ -2728,6 +2774,9 @@ function OpenTuiApp(props) {
|
|
|
2728
2774
|
function onPromptContentChange(value) {
|
|
2729
2775
|
const nextValue = typeof value === "string" ? value : readPromptText();
|
|
2730
2776
|
promptText = nextValue;
|
|
2777
|
+
if (!applyingComposerImageReplacement) {
|
|
2778
|
+
void applyComposerImagePathReplacement(nextValue);
|
|
2779
|
+
}
|
|
2731
2780
|
if (promptHistoryIndex !== undefined
|
|
2732
2781
|
&& nextValue !== (promptHistory[promptHistoryIndex] ?? "")) {
|
|
2733
2782
|
resetPromptHistoryBrowse();
|
|
@@ -2995,8 +3044,62 @@ function OpenTuiApp(props) {
|
|
|
2995
3044
|
setPromptText(`/${skillName} `);
|
|
2996
3045
|
redrawDock();
|
|
2997
3046
|
}
|
|
3047
|
+
async function applyComposerImagePathReplacement(snapshot) {
|
|
3048
|
+
const seq = ++composerImageResolutionSeq;
|
|
3049
|
+
const result = await resolveComposerImagePaths(snapshot, { labelStart: nextImageAttachmentIndex });
|
|
3050
|
+
if (seq !== composerImageResolutionSeq)
|
|
3051
|
+
return;
|
|
3052
|
+
if (result.attachments.length === 0)
|
|
3053
|
+
return;
|
|
3054
|
+
if ((readPromptText() || promptText) !== snapshot)
|
|
3055
|
+
return;
|
|
3056
|
+
for (const attachment of result.attachments) {
|
|
3057
|
+
pendingImageAttachments.set(attachment.label, attachment);
|
|
3058
|
+
}
|
|
3059
|
+
nextImageAttachmentIndex = Math.max(nextImageAttachmentIndex, result.nextLabelIndex);
|
|
3060
|
+
applyingComposerImageReplacement = true;
|
|
3061
|
+
try {
|
|
3062
|
+
setPromptText(result.text);
|
|
3063
|
+
}
|
|
3064
|
+
finally {
|
|
3065
|
+
applyingComposerImageReplacement = false;
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
async function expandTextParts(parts) {
|
|
3069
|
+
const expandedParts = [];
|
|
3070
|
+
for (const part of parts) {
|
|
3071
|
+
if (part.type !== "text") {
|
|
3072
|
+
expandedParts.push(part);
|
|
3073
|
+
continue;
|
|
3074
|
+
}
|
|
3075
|
+
const expansion = await expandAtMentions(part.text, props.args.cwd);
|
|
3076
|
+
if (expansion.missing.length)
|
|
3077
|
+
addMessage("error", `Could not resolve @mention: ${expansion.missing.join(", ")}`);
|
|
3078
|
+
for (const skipped of expansion.skipped)
|
|
3079
|
+
addMessage("error", `Skipped @${skipped.path}: ${skipped.reason}`);
|
|
3080
|
+
expandedParts.push({ type: "text", text: expansion.text });
|
|
3081
|
+
}
|
|
3082
|
+
return expandedParts;
|
|
3083
|
+
}
|
|
2998
3084
|
async function handleInput(input) {
|
|
2999
3085
|
setNotice("");
|
|
3086
|
+
const labeledInput = buildImageContentPartsFromLabels(input, pendingImageAttachments);
|
|
3087
|
+
if (labeledInput.actualInput) {
|
|
3088
|
+
await runAgentInput(await expandTextParts(labeledInput.actualInput), labeledInput.displayInput);
|
|
3089
|
+
for (const label of labeledInput.usedLabels)
|
|
3090
|
+
pendingImageAttachments.delete(label);
|
|
3091
|
+
return;
|
|
3092
|
+
}
|
|
3093
|
+
const imageInput = await resolveImageInput(input, { labelStart: nextImageAttachmentIndex });
|
|
3094
|
+
for (const error of imageInput.errors)
|
|
3095
|
+
addMessage("error", `Skipped image: ${error}`);
|
|
3096
|
+
if (imageInput.attachments.length > 0) {
|
|
3097
|
+
await runAgentInput(await expandTextParts(imageInput.actualInput), imageInput.displayInput);
|
|
3098
|
+
nextImageAttachmentIndex += imageInput.attachments.length;
|
|
3099
|
+
return;
|
|
3100
|
+
}
|
|
3101
|
+
if (imageInput.imagePathCount > 0)
|
|
3102
|
+
return;
|
|
3000
3103
|
if (input.startsWith("/")) {
|
|
3001
3104
|
const skillInvocation = parseSkillInvocation(input, skills);
|
|
3002
3105
|
if (skillInvocation) {
|
|
@@ -3016,12 +3119,7 @@ function OpenTuiApp(props) {
|
|
|
3016
3119
|
}
|
|
3017
3120
|
async function executeSlash(input) {
|
|
3018
3121
|
if (/^\/(?:thinking|toggle-thinking)(?:\s|$)/.test(input.trim())) {
|
|
3019
|
-
|
|
3020
|
-
const next = !prev;
|
|
3021
|
-
setNotice(next ? "Thinking blocks visible" : "Thinking blocks hidden");
|
|
3022
|
-
return next;
|
|
3023
|
-
});
|
|
3024
|
-
redrawTranscript();
|
|
3122
|
+
toggleThinkingVisibility();
|
|
3025
3123
|
return true;
|
|
3026
3124
|
}
|
|
3027
3125
|
const wasHomeSurfaceActive = isHomeSurfaceActive();
|
|
@@ -3071,7 +3169,9 @@ function OpenTuiApp(props) {
|
|
|
3071
3169
|
const isCompactResult = result.startsWith("✓ Compaction complete");
|
|
3072
3170
|
if (isCompactResult) {
|
|
3073
3171
|
setNotice(result);
|
|
3074
|
-
|
|
3172
|
+
displayMessages = reconstructDisplayMessages(props.agent.messages);
|
|
3173
|
+
streamingDisplay = undefined;
|
|
3174
|
+
redrawTranscript(undefined, displayMessages);
|
|
3075
3175
|
setTimeout(() => setNotice(""), 4000);
|
|
3076
3176
|
}
|
|
3077
3177
|
else {
|
|
@@ -3461,6 +3561,8 @@ function OpenTuiApp(props) {
|
|
|
3461
3561
|
let assistantContent = "";
|
|
3462
3562
|
let assistantReasoning = "";
|
|
3463
3563
|
const toolCalls = [];
|
|
3564
|
+
let currentTurnHasToolCall = false;
|
|
3565
|
+
let turnStartedAt;
|
|
3464
3566
|
let runError;
|
|
3465
3567
|
let runCancelled = false;
|
|
3466
3568
|
try {
|
|
@@ -3469,46 +3571,72 @@ function OpenTuiApp(props) {
|
|
|
3469
3571
|
assistantContent = "";
|
|
3470
3572
|
assistantReasoning = "";
|
|
3471
3573
|
toolCalls.length = 0;
|
|
3574
|
+
currentTurnHasToolCall = false;
|
|
3575
|
+
turnStartedAt = Date.now();
|
|
3472
3576
|
redrawTranscript({
|
|
3473
3577
|
role: "assistant",
|
|
3474
3578
|
content: "",
|
|
3475
3579
|
status: "thinking",
|
|
3476
3580
|
streaming: true,
|
|
3581
|
+
turnStartedAt,
|
|
3477
3582
|
});
|
|
3478
3583
|
}
|
|
3479
3584
|
else if (event.type === "text_delta") {
|
|
3480
3585
|
assistantContent += event.content;
|
|
3586
|
+
}
|
|
3587
|
+
else if (event.type === "reasoning_delta") {
|
|
3588
|
+
assistantReasoning += event.content;
|
|
3481
3589
|
redrawTranscript({
|
|
3482
3590
|
role: "assistant",
|
|
3483
|
-
content:
|
|
3591
|
+
content: "",
|
|
3484
3592
|
reasoning: assistantReasoning || undefined,
|
|
3485
3593
|
toolCalls: toolCalls.length ? [...toolCalls] : undefined,
|
|
3486
|
-
status: "
|
|
3594
|
+
status: "thinking",
|
|
3487
3595
|
streaming: true,
|
|
3596
|
+
turnStartedAt,
|
|
3488
3597
|
});
|
|
3489
3598
|
}
|
|
3490
|
-
else if (event.type === "
|
|
3491
|
-
|
|
3599
|
+
else if (event.type === "tool_call_start") {
|
|
3600
|
+
currentTurnHasToolCall = true;
|
|
3492
3601
|
redrawTranscript({
|
|
3493
3602
|
role: "assistant",
|
|
3494
|
-
content:
|
|
3603
|
+
content: "",
|
|
3495
3604
|
reasoning: assistantReasoning || undefined,
|
|
3496
3605
|
toolCalls: toolCalls.length ? [...toolCalls] : undefined,
|
|
3497
|
-
status: "thinking",
|
|
3606
|
+
status: toolCalls.length ? undefined : "thinking",
|
|
3498
3607
|
streaming: true,
|
|
3608
|
+
turnStartedAt,
|
|
3499
3609
|
});
|
|
3500
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
|
+
}
|
|
3501
3617
|
else if (event.type === "tool_start") {
|
|
3502
|
-
|
|
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
|
+
}
|
|
3503
3630
|
if (event.name === "question") {
|
|
3504
3631
|
scheduleQuestionSync();
|
|
3505
3632
|
}
|
|
3506
3633
|
redrawTranscript({
|
|
3507
3634
|
role: "assistant",
|
|
3508
|
-
content:
|
|
3635
|
+
content: "",
|
|
3509
3636
|
reasoning: assistantReasoning || undefined,
|
|
3510
3637
|
toolCalls: [...toolCalls],
|
|
3511
3638
|
streaming: true,
|
|
3639
|
+
turnStartedAt,
|
|
3512
3640
|
});
|
|
3513
3641
|
}
|
|
3514
3642
|
else if (event.type === "tool_end") {
|
|
@@ -3518,12 +3646,14 @@ function OpenTuiApp(props) {
|
|
|
3518
3646
|
call.isError = event.result.isError;
|
|
3519
3647
|
call.metadata = event.result.metadata;
|
|
3520
3648
|
call.status = event.result.isError ? "error" : "completed";
|
|
3649
|
+
call.completedAt = Date.now();
|
|
3521
3650
|
redrawTranscript({
|
|
3522
3651
|
role: "assistant",
|
|
3523
|
-
content: assistantContent,
|
|
3652
|
+
content: currentTurnHasToolCall ? "" : assistantContent,
|
|
3524
3653
|
reasoning: assistantReasoning || undefined,
|
|
3525
3654
|
toolCalls: [...toolCalls],
|
|
3526
3655
|
streaming: true,
|
|
3656
|
+
turnStartedAt,
|
|
3527
3657
|
});
|
|
3528
3658
|
}
|
|
3529
3659
|
if (event.name === "question") {
|
|
@@ -3532,6 +3662,30 @@ function OpenTuiApp(props) {
|
|
|
3532
3662
|
refreshGitSidebar();
|
|
3533
3663
|
syncSidebarLsp();
|
|
3534
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
|
+
}
|
|
3535
3689
|
else if (event.type === "todos_updated") {
|
|
3536
3690
|
setTodos(event.todos);
|
|
3537
3691
|
syncSidebarTodos(event.todos);
|
|
@@ -3558,9 +3712,12 @@ function OpenTuiApp(props) {
|
|
|
3558
3712
|
bumpSidebar();
|
|
3559
3713
|
const assistantMessage = {
|
|
3560
3714
|
role: "assistant",
|
|
3561
|
-
content: assistantContent,
|
|
3715
|
+
content: currentTurnHasToolCall ? "" : assistantContent,
|
|
3562
3716
|
reasoning: assistantReasoning || undefined,
|
|
3563
3717
|
toolCalls: toolCalls.length ? [...toolCalls] : undefined,
|
|
3718
|
+
turnStartedAt,
|
|
3719
|
+
turnCompletedAt: Date.now(),
|
|
3720
|
+
turnUsage: event.usage,
|
|
3564
3721
|
};
|
|
3565
3722
|
const nextMessages = hasRenderableMessage(assistantMessage)
|
|
3566
3723
|
? [...displayMessages, assistantMessage]
|
|
@@ -3570,6 +3727,7 @@ function OpenTuiApp(props) {
|
|
|
3570
3727
|
assistantContent = "";
|
|
3571
3728
|
assistantReasoning = "";
|
|
3572
3729
|
toolCalls.length = 0;
|
|
3730
|
+
turnStartedAt = undefined;
|
|
3573
3731
|
streamingDisplay = undefined;
|
|
3574
3732
|
}
|
|
3575
3733
|
}
|
|
@@ -3672,7 +3830,7 @@ function OpenTuiApp(props) {
|
|
|
3672
3830
|
paddingLeft: 2,
|
|
3673
3831
|
paddingRight: 2,
|
|
3674
3832
|
}, [
|
|
3675
|
-
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 || " "))),
|
|
3676
3834
|
h("box", { height: 1, minHeight: 0, flexShrink: 1 }),
|
|
3677
3835
|
h("box", {
|
|
3678
3836
|
ref: (ref) => {
|
|
@@ -4417,7 +4575,6 @@ function OpenTuiApp(props) {
|
|
|
4417
4575
|
if (isNewHost)
|
|
4418
4576
|
transcriptState.entries = [];
|
|
4419
4577
|
updateTranscriptHost(ref, transcriptState, currentTranscriptMessages(streamingDisplay), transcriptOptions(), props.syntaxStyle, props.subtleSyntaxStyle);
|
|
4420
|
-
syncThinkingSpinner();
|
|
4421
4578
|
syncPromptSurfaces(isNewHost);
|
|
4422
4579
|
if (isNewHost)
|
|
4423
4580
|
scheduleTranscriptScrollAfterUpdate(transcriptScrollFollowing, 0);
|
|
@@ -4756,19 +4913,31 @@ function renderAssistantMessage(message, syntaxStyle, subtleSyntaxStyle, showThi
|
|
|
4756
4913
|
borderColor: theme.messageThinkingBorder,
|
|
4757
4914
|
flexDirection: "column",
|
|
4758
4915
|
flexShrink: 0,
|
|
4759
|
-
}, renderMarkdownContent(formatThinkingMarkdown(visibleReasoning), subtleSyntaxStyle, {
|
|
4916
|
+
}, h("text", { content: thinkingLabelContent(message.streaming === true, reasoningElapsedMs(message)), fg: theme.messageThinkingText, wrapMode: "none" }), renderMarkdownContent(formatThinkingMarkdown(visibleReasoning), subtleSyntaxStyle, {
|
|
4760
4917
|
streaming: message.streaming === true,
|
|
4761
|
-
fg: theme.
|
|
4918
|
+
fg: theme.messageThinkingContentText,
|
|
4762
4919
|
})));
|
|
4763
4920
|
}
|
|
4764
|
-
|
|
4921
|
+
const toolCalls = message.toolCalls ?? [];
|
|
4922
|
+
for (const tool of toolCalls)
|
|
4765
4923
|
children.push(renderTool(tool, syntaxStyle, width));
|
|
4766
|
-
|
|
4767
|
-
|
|
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, {
|
|
4768
4930
|
streaming: message.streaming === true,
|
|
4769
4931
|
fg: theme.messageAssistantText,
|
|
4770
4932
|
})));
|
|
4771
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
|
+
}
|
|
4772
4941
|
if (!children.length)
|
|
4773
4942
|
return null;
|
|
4774
4943
|
return h("box", { flexDirection: "column", flexShrink: 0 }, children);
|
|
@@ -4847,12 +5016,23 @@ function updateTranscriptHost(host, state, messages, options, syntaxStyle, subtl
|
|
|
4847
5016
|
}
|
|
4848
5017
|
for (const [index, message] of visibleMessages.entries()) {
|
|
4849
5018
|
const key = transcriptMessageKey(message, index);
|
|
4850
|
-
|
|
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
|
+
}
|
|
4851
5026
|
const compactionExpanded = state.expandedCompactions.has(key);
|
|
4852
|
-
const signature = transcriptMessageSignature(message,
|
|
5027
|
+
const signature = transcriptMessageSignature(message, compactionExpanded);
|
|
4853
5028
|
const previous = state.entries[index];
|
|
4854
5029
|
if (previous?.key === key && previous.signature === signature) {
|
|
4855
|
-
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
|
+
});
|
|
4856
5036
|
nextEntries.push(previous);
|
|
4857
5037
|
continue;
|
|
4858
5038
|
}
|
|
@@ -4860,7 +5040,7 @@ function updateTranscriptHost(host, state, messages, options, syntaxStyle, subtl
|
|
|
4860
5040
|
host.remove(previous.node.id);
|
|
4861
5041
|
previous.node.destroyRecursively();
|
|
4862
5042
|
}
|
|
4863
|
-
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);
|
|
4864
5044
|
if (entry) {
|
|
4865
5045
|
host.add(entry.node, index);
|
|
4866
5046
|
nextEntries.push(entry);
|
|
@@ -4903,34 +5083,23 @@ function clearTranscriptEntries(host, state) {
|
|
|
4903
5083
|
function transcriptMessageKey(message, index) {
|
|
4904
5084
|
return `${index}:${message.role}`;
|
|
4905
5085
|
}
|
|
4906
|
-
function transcriptMessageSignature(message,
|
|
5086
|
+
function transcriptMessageSignature(message, compactionExpanded = false) {
|
|
4907
5087
|
if (message.role !== "assistant")
|
|
4908
5088
|
return message.role;
|
|
4909
5089
|
if (message.syntheticKind === "ui_compact_card") {
|
|
4910
5090
|
return `compaction:${compactionExpanded ? "expanded" : "collapsed"}:${message.compactionMeta?.turns ?? 0}`;
|
|
4911
5091
|
}
|
|
4912
5092
|
const modelSwitch = parseModelSwitchMessage(message.content);
|
|
4913
|
-
const
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
5093
|
+
const pureModelSwitch = modelSwitch && !message.reasoning?.trim() && !(message.toolCalls?.length);
|
|
5094
|
+
if (pureModelSwitch) {
|
|
5095
|
+
return `assistant:model-switch:${hashString(modelSwitch)}`;
|
|
5096
|
+
}
|
|
4917
5097
|
return [
|
|
4918
5098
|
message.role,
|
|
4919
|
-
|
|
4920
|
-
message.status ?? "idle",
|
|
4921
|
-
visibleReasoning ? (thinkingExpanded ? "reasoning-expanded" : "reasoning-collapsed") : "no-reasoning",
|
|
4922
|
-
message.content.trim() ? "content" : "no-content",
|
|
4923
|
-
tools,
|
|
5099
|
+
"standard",
|
|
4924
5100
|
].join(":");
|
|
4925
5101
|
}
|
|
4926
|
-
function
|
|
4927
|
-
let hash = 5381;
|
|
4928
|
-
for (let index = 0; index < value.length; index++) {
|
|
4929
|
-
hash = ((hash << 5) + hash) ^ value.charCodeAt(index);
|
|
4930
|
-
}
|
|
4931
|
-
return (hash >>> 0).toString(36);
|
|
4932
|
-
}
|
|
4933
|
-
function updateMessageEntry(entry, message, showThinking = true, thinkingExpanded = false, compactionExpanded = false) {
|
|
5102
|
+
function updateMessageEntry(entry, message, showThinking = true, compactionExpanded = false, assistantOptions) {
|
|
4934
5103
|
if (message.role === "user") {
|
|
4935
5104
|
if (entry.refs.userText)
|
|
4936
5105
|
entry.refs.userText.content = message.content || " ";
|
|
@@ -4954,21 +5123,150 @@ function updateMessageEntry(entry, message, showThinking = true, thinkingExpande
|
|
|
4954
5123
|
}
|
|
4955
5124
|
return;
|
|
4956
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;
|
|
4957
5136
|
if (entry.refs.statusText) {
|
|
4958
|
-
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;
|
|
4959
5141
|
}
|
|
4960
5142
|
if (entry.refs.reasoningToggleText) {
|
|
4961
|
-
entry.refs.reasoningExpanded = thinkingExpanded;
|
|
4962
5143
|
entry.refs.reasoningStreaming = message.streaming === true;
|
|
4963
|
-
entry.refs.reasoningToggleText.content =
|
|
5144
|
+
entry.refs.reasoningToggleText.content = visibleReasoning
|
|
5145
|
+
? thinkingLabelContent(message.streaming === true, reasoningElapsedMs(message))
|
|
5146
|
+
: new StyledText([fg(theme.messageThinkingText)("")]);
|
|
4964
5147
|
}
|
|
4965
5148
|
if (entry.refs.reasoningMarkdown) {
|
|
4966
|
-
entry.refs.reasoningMarkdown
|
|
4967
|
-
|
|
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
|
+
}
|
|
4968
5163
|
}
|
|
4969
5164
|
if (entry.refs.contentMarkdown) {
|
|
4970
|
-
entry.refs.contentMarkdown
|
|
4971
|
-
|
|
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);
|
|
4972
5270
|
}
|
|
4973
5271
|
}
|
|
4974
5272
|
function createBox(ctx, options, children = []) {
|
|
@@ -4992,6 +5290,7 @@ function createMarkdown(ctx, content, syntaxStyle, options) {
|
|
|
4992
5290
|
content,
|
|
4993
5291
|
syntaxStyle,
|
|
4994
5292
|
treeSitterClient,
|
|
5293
|
+
renderNode: createSemanticMarkdownRenderNode(ctx, options?.fg ?? theme.messageAssistantText),
|
|
4995
5294
|
streaming: options?.streaming === true,
|
|
4996
5295
|
conceal: true,
|
|
4997
5296
|
concealCode: false,
|
|
@@ -5011,6 +5310,87 @@ function createMarkdown(ctx, content, syntaxStyle, options) {
|
|
|
5011
5310
|
},
|
|
5012
5311
|
});
|
|
5013
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
|
+
}
|
|
5014
5394
|
function createDiffRenderable(ctx, diff, filePath, syntaxStyle, width = 80) {
|
|
5015
5395
|
return new DiffRenderable(ctx, {
|
|
5016
5396
|
diff,
|
|
@@ -5073,6 +5453,24 @@ function createCodeBlockRenderable(ctx, content, filePath, syntaxStyle) {
|
|
|
5073
5453
|
lineNumbers.add(code);
|
|
5074
5454
|
return lineNumbers;
|
|
5075
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
|
+
}
|
|
5076
5474
|
function renderCodeBlockContent(content, filePath, syntaxStyle) {
|
|
5077
5475
|
return h("line_number", { fg: theme.textMuted, minWidth: 3, paddingRight: 1 }, h("code", {
|
|
5078
5476
|
content,
|
|
@@ -5084,14 +5482,14 @@ function renderCodeBlockContent(content, filePath, syntaxStyle) {
|
|
|
5084
5482
|
width: "100%",
|
|
5085
5483
|
}));
|
|
5086
5484
|
}
|
|
5087
|
-
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) {
|
|
5088
5486
|
if (message.role === "user")
|
|
5089
5487
|
return createUserEntry(ctx, message, index, key, signature);
|
|
5090
5488
|
if (message.role === "error")
|
|
5091
5489
|
return createErrorEntry(ctx, message, key, signature);
|
|
5092
5490
|
if (message.syntheticKind === "ui_compact_card")
|
|
5093
5491
|
return createCompactionCardEntry(ctx, message, key, signature, compactionExpanded, onToggleCompaction);
|
|
5094
|
-
return createAssistantEntry(ctx, message, syntaxStyle, subtleSyntaxStyle, key, signature, showThinking, width,
|
|
5492
|
+
return createAssistantEntry(ctx, message, syntaxStyle, subtleSyntaxStyle, key, signature, showThinking, width, expandedWrites, onToggleWrite);
|
|
5095
5493
|
}
|
|
5096
5494
|
function createUserEntry(ctx, message, index, key, signature) {
|
|
5097
5495
|
const refs = {};
|
|
@@ -5136,7 +5534,7 @@ function createErrorEntry(ctx, message, key, signature) {
|
|
|
5136
5534
|
}, [text]);
|
|
5137
5535
|
return { key, signature, node, refs };
|
|
5138
5536
|
}
|
|
5139
|
-
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) {
|
|
5140
5538
|
const modelSwitch = parseModelSwitchMessage(message.content);
|
|
5141
5539
|
if (modelSwitch && !message.reasoning?.trim() && !(message.toolCalls?.length)) {
|
|
5142
5540
|
return createModelSwitchEntry(ctx, modelSwitch, key, signature);
|
|
@@ -5144,70 +5542,121 @@ function createAssistantEntry(ctx, message, syntaxStyle, subtleSyntaxStyle, key,
|
|
|
5144
5542
|
const children = [];
|
|
5145
5543
|
const refs = {};
|
|
5146
5544
|
const visibleReasoning = showThinking ? message.reasoning?.trim() : "";
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
borderColor: theme.messageThinkingBorder,
|
|
5184
|
-
flexDirection: "column",
|
|
5185
|
-
flexShrink: 0,
|
|
5186
|
-
}, reasoningChildren));
|
|
5187
|
-
}
|
|
5188
|
-
for (const tool of message.toolCalls ?? [])
|
|
5189
|
-
children.push(createToolRenderable(ctx, tool, syntaxStyle, width));
|
|
5190
|
-
if (message.content.trim()) {
|
|
5191
|
-
const markdown = createMarkdown(ctx, message.content.trim(), syntaxStyle, {
|
|
5192
|
-
streaming: message.streaming === true,
|
|
5193
|
-
fg: theme.messageAssistantText,
|
|
5194
|
-
});
|
|
5195
|
-
refs.contentMarkdown = markdown;
|
|
5196
|
-
children.push(createBox(ctx, {
|
|
5197
|
-
paddingLeft: 3,
|
|
5198
|
-
marginTop: 1,
|
|
5199
|
-
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, {
|
|
5200
5581
|
flexShrink: 0,
|
|
5201
|
-
}, [
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
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 = {
|
|
5206
5642
|
key,
|
|
5207
5643
|
signature,
|
|
5208
5644
|
node: createBox(ctx, { flexDirection: "column", flexShrink: 0 }, children),
|
|
5209
5645
|
refs,
|
|
5210
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
|
+
]);
|
|
5211
5660
|
}
|
|
5212
5661
|
function createCompactionCardEntry(ctx, message, key, signature, expanded, onToggle) {
|
|
5213
5662
|
const refs = {};
|
|
@@ -5325,7 +5774,7 @@ function createTodoWriteRenderable(ctx, tool) {
|
|
|
5325
5774
|
flexDirection: "column",
|
|
5326
5775
|
flexShrink: 0,
|
|
5327
5776
|
}, [
|
|
5328
|
-
createText(ctx,
|
|
5777
|
+
createText(ctx, `→ Planning tasks...`, { fg: toolColor(tool) }),
|
|
5329
5778
|
]);
|
|
5330
5779
|
}
|
|
5331
5780
|
return createBox(ctx, {
|
|
@@ -5352,90 +5801,26 @@ function createTodoWriteRenderable(ctx, tool) {
|
|
|
5352
5801
|
}),
|
|
5353
5802
|
]);
|
|
5354
5803
|
}
|
|
5355
|
-
function createToolRenderable(ctx, tool, syntaxStyle, width = 80) {
|
|
5804
|
+
function createToolRenderable(ctx, tool, syntaxStyle, width = 80, writeExpanded = false, onToggleWrite) {
|
|
5356
5805
|
if (tool.name === "question") {
|
|
5357
5806
|
return createQuestionToolRenderable(ctx, tool);
|
|
5358
5807
|
}
|
|
5359
5808
|
if (tool.name === "todo_write") {
|
|
5360
5809
|
return createTodoWriteRenderable(ctx, tool);
|
|
5361
5810
|
}
|
|
5362
|
-
const
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
}
|
|
5373
|
-
createText(ctx, new StyledText([
|
|
5374
|
-
fg(color)(`${icon} ${displayToolName(tool.name)}`),
|
|
5375
|
-
fg(theme.toolText)(header ? ` ${header}` : ""),
|
|
5376
|
-
])),
|
|
5377
|
-
createBox(ctx, {
|
|
5378
|
-
paddingLeft: 1,
|
|
5379
|
-
marginTop: 1,
|
|
5380
|
-
border: ["left"],
|
|
5381
|
-
borderColor: theme.borderSubtle,
|
|
5382
|
-
flexDirection: "column",
|
|
5383
|
-
flexShrink: 0,
|
|
5384
|
-
}, [createDiffRenderable(ctx, diff, toolPath(tool), syntaxStyle, width)]),
|
|
5385
|
-
]);
|
|
5386
|
-
}
|
|
5387
|
-
if (!tool.isError && tool.name === "write" && typeof tool.args?.content === "string" && isToolFinished(tool)) {
|
|
5388
|
-
return createBox(ctx, {
|
|
5389
|
-
paddingLeft: 3,
|
|
5390
|
-
marginTop: 1,
|
|
5391
|
-
flexDirection: "column",
|
|
5392
|
-
flexShrink: 0,
|
|
5393
|
-
}, [
|
|
5394
|
-
createText(ctx, new StyledText([
|
|
5395
|
-
fg(color)(`${icon} ${displayToolName(tool.name)}`),
|
|
5396
|
-
fg(theme.toolText)(header ? ` ${header}` : ""),
|
|
5397
|
-
])),
|
|
5398
|
-
createBox(ctx, {
|
|
5399
|
-
paddingLeft: 1,
|
|
5400
|
-
marginTop: 1,
|
|
5401
|
-
border: ["left"],
|
|
5402
|
-
borderColor: theme.borderSubtle,
|
|
5403
|
-
flexDirection: "column",
|
|
5404
|
-
flexShrink: 0,
|
|
5405
|
-
}, [createCodeBlockRenderable(ctx, tool.args.content, toolPath(tool), syntaxStyle)]),
|
|
5406
|
-
]);
|
|
5407
|
-
}
|
|
5408
|
-
const chunks = [
|
|
5409
|
-
fg(color)(`${isToolFinished(tool) ? "" : "~ "}${icon} ${displayToolName(tool.name)}`),
|
|
5410
|
-
];
|
|
5411
|
-
if (header)
|
|
5412
|
-
chunks.push(fg(theme.toolText)(` ${header}`));
|
|
5413
|
-
if (tool.result) {
|
|
5414
|
-
chunks.push(fg(theme.text)("\n"));
|
|
5415
|
-
chunks.push(fg(theme.borderSubtle)(" "));
|
|
5416
|
-
chunks.push(fg(tool.isError ? theme.toolError : theme.textMuted)(summarizeToolResult(tool)));
|
|
5417
|
-
const preview = toolPreview(tool);
|
|
5418
|
-
if (preview) {
|
|
5419
|
-
for (const line of preview.lines) {
|
|
5420
|
-
chunks.push(fg(theme.text)("\n"));
|
|
5421
|
-
chunks.push(fg(theme.borderSubtle)(" "));
|
|
5422
|
-
chunks.push(fg(theme.toolText)(line));
|
|
5423
|
-
}
|
|
5424
|
-
if (preview.omitted > 0) {
|
|
5425
|
-
chunks.push(fg(theme.text)("\n"));
|
|
5426
|
-
chunks.push(fg(theme.borderSubtle)(" "));
|
|
5427
|
-
chunks.push(fg(theme.textMuted)(`+ ${preview.omitted} more`));
|
|
5428
|
-
}
|
|
5429
|
-
}
|
|
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
|
+
});
|
|
5430
5822
|
}
|
|
5431
|
-
|
|
5432
|
-
paddingLeft: 3,
|
|
5433
|
-
marginTop: 1,
|
|
5434
|
-
flexDirection: "column",
|
|
5435
|
-
flexShrink: 0,
|
|
5436
|
-
}, [
|
|
5437
|
-
createText(ctx, new StyledText(chunks), { wrapMode: "word" }),
|
|
5438
|
-
]);
|
|
5823
|
+
throw new Error(`No renderer for tool '${tool.name}'`);
|
|
5439
5824
|
}
|
|
5440
5825
|
function createQuestionToolRenderable(ctx, tool) {
|
|
5441
5826
|
const questions = questionToolQuestions(tool);
|
|
@@ -5448,7 +5833,7 @@ function createQuestionToolRenderable(ctx, tool) {
|
|
|
5448
5833
|
flexDirection: "column",
|
|
5449
5834
|
flexShrink: 0,
|
|
5450
5835
|
}, [
|
|
5451
|
-
createText(ctx,
|
|
5836
|
+
createText(ctx, `→ ${rejected ? "Asked" : "Asking"} questions...`, {
|
|
5452
5837
|
fg: rejected ? theme.textMuted : toolColor(tool),
|
|
5453
5838
|
attributes: rejected ? TextAttributes.STRIKETHROUGH : undefined,
|
|
5454
5839
|
}),
|
|
@@ -5494,16 +5879,22 @@ function renderTool(tool, syntaxStyle, width = 80) {
|
|
|
5494
5879
|
if (tool.name === "question") {
|
|
5495
5880
|
return renderQuestionTool(tool);
|
|
5496
5881
|
}
|
|
5497
|
-
const icon = tool
|
|
5882
|
+
const icon = toolStateIcon(tool);
|
|
5498
5883
|
const color = toolColor(tool);
|
|
5499
5884
|
const diff = extractToolDiff(tool);
|
|
5500
5885
|
if (diff && !tool.isError && tool.name === "edit") {
|
|
5501
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)));
|
|
5502
5887
|
}
|
|
5503
|
-
if (
|
|
5504
|
-
|
|
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));
|
|
5505
5896
|
}
|
|
5506
|
-
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);
|
|
5507
5898
|
}
|
|
5508
5899
|
function renderQuestionTool(tool) {
|
|
5509
5900
|
const questions = questionToolQuestions(tool);
|
|
@@ -5513,7 +5904,7 @@ function renderQuestionTool(tool) {
|
|
|
5513
5904
|
return h("box", { paddingLeft: 3, marginTop: 1, flexDirection: "column", flexShrink: 0 }, h("text", {
|
|
5514
5905
|
fg: rejected ? theme.textMuted : toolColor(tool),
|
|
5515
5906
|
attributes: rejected ? TextAttributes.STRIKETHROUGH : undefined,
|
|
5516
|
-
},
|
|
5907
|
+
}, `→ ${rejected ? "Asked" : "Asking"} questions...`));
|
|
5517
5908
|
}
|
|
5518
5909
|
return h("box", {
|
|
5519
5910
|
border: ["left"],
|
|
@@ -5744,15 +6135,49 @@ function toSelectOption(item) {
|
|
|
5744
6135
|
value: item.value,
|
|
5745
6136
|
};
|
|
5746
6137
|
}
|
|
6138
|
+
function nextImageLabelIndex(messages) {
|
|
6139
|
+
let max = 0;
|
|
6140
|
+
for (const message of messages) {
|
|
6141
|
+
for (const match of message.content.matchAll(imageAttachmentLabelPattern())) {
|
|
6142
|
+
max = Math.max(max, Number(match[1] ?? 0));
|
|
6143
|
+
}
|
|
6144
|
+
}
|
|
6145
|
+
return max + 1;
|
|
6146
|
+
}
|
|
6147
|
+
function imageExtensionFromUrl(url) {
|
|
6148
|
+
const mediaMatch = url.match(/^data:image\/([^;,]+)/i);
|
|
6149
|
+
const media = mediaMatch?.[1]?.toLowerCase();
|
|
6150
|
+
if (media === "jpeg")
|
|
6151
|
+
return "jpg";
|
|
6152
|
+
if (media === "png" || media === "webp" || media === "gif" || media === "bmp")
|
|
6153
|
+
return media;
|
|
6154
|
+
const pathMatch = url.match(/\.([a-z0-9]+)(?:[?#].*)?$/i);
|
|
6155
|
+
return pathMatch?.[1]?.toLowerCase() || "png";
|
|
6156
|
+
}
|
|
6157
|
+
function formatDisplayContentParts(content, labelStart) {
|
|
6158
|
+
const text = content
|
|
6159
|
+
.filter((part) => part.type === "text")
|
|
6160
|
+
.map((part) => part.text)
|
|
6161
|
+
.join("\n")
|
|
6162
|
+
.trim();
|
|
6163
|
+
let imageIndex = labelStart;
|
|
6164
|
+
const imageLines = content
|
|
6165
|
+
.filter((part) => part.type === "image_url")
|
|
6166
|
+
.map((part) => `[image#${imageIndex++}.${imageExtensionFromUrl(part.image_url.url)}]`);
|
|
6167
|
+
return [text, ...imageLines].filter(Boolean).join("\n") || "(multimedia)";
|
|
6168
|
+
}
|
|
5747
6169
|
function reconstructDisplayMessages(agentMessages) {
|
|
5748
6170
|
const result = [];
|
|
5749
6171
|
for (const message of agentMessages) {
|
|
5750
|
-
if (message.role === "system" || message.role === "tool")
|
|
6172
|
+
if (message.role === "system" || message.role === "meta" || message.role === "tool")
|
|
5751
6173
|
continue;
|
|
5752
6174
|
if (message.role === "user") {
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
6175
|
+
result.push({
|
|
6176
|
+
role: "user",
|
|
6177
|
+
content: typeof message.content === "string"
|
|
6178
|
+
? message.content
|
|
6179
|
+
: formatDisplayContentParts(message.content, nextImageLabelIndex(result)),
|
|
6180
|
+
});
|
|
5756
6181
|
continue;
|
|
5757
6182
|
}
|
|
5758
6183
|
const toolCalls = [];
|
|
@@ -5835,7 +6260,9 @@ function formatTranscript(messages, options) {
|
|
|
5835
6260
|
if (visibleReasoning) {
|
|
5836
6261
|
appendBlank();
|
|
5837
6262
|
append("│ ", theme.messageThinkingBorder);
|
|
5838
|
-
|
|
6263
|
+
chunks.push(fg(theme.messageThinkingText)(italic("Thinking\n")));
|
|
6264
|
+
append("│ ", theme.messageThinkingBorder);
|
|
6265
|
+
appendLine(truncate(formatThinkingMarkdown(visibleReasoning), 500), theme.messageThinkingContentText);
|
|
5839
6266
|
}
|
|
5840
6267
|
if (message.status && !visibleReasoning && !message.content.trim() && !(message.toolCalls?.length)) {
|
|
5841
6268
|
appendBlank();
|
|
@@ -5846,7 +6273,7 @@ function formatTranscript(messages, options) {
|
|
|
5846
6273
|
appendBlank();
|
|
5847
6274
|
const icon = tool.name === "bash" ? "$" : tool.name === "edit" || tool.name === "write" ? "✎" : "●";
|
|
5848
6275
|
const color = toolColor(tool);
|
|
5849
|
-
append(` ${
|
|
6276
|
+
append(` ${icon} `, color);
|
|
5850
6277
|
append(displayToolName(tool.name), color);
|
|
5851
6278
|
const header = toolHeader(tool);
|
|
5852
6279
|
if (header)
|
|
@@ -5896,7 +6323,7 @@ function renderHomeState(input) {
|
|
|
5896
6323
|
flexDirection: "column",
|
|
5897
6324
|
alignItems: "center",
|
|
5898
6325
|
justifyContent: "center",
|
|
5899
|
-
}, 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));
|
|
5900
6327
|
}
|
|
5901
6328
|
function hasRenderableMessage(message, showThinking = true) {
|
|
5902
6329
|
if (message.role === "error")
|
|
@@ -6072,6 +6499,11 @@ function displayToolName(name) {
|
|
|
6072
6499
|
glob: "Glob",
|
|
6073
6500
|
web_fetch: "WebFetch",
|
|
6074
6501
|
web_search: "WebSearch",
|
|
6502
|
+
subagent: "Subagent",
|
|
6503
|
+
spawn_agent: "SpawnAgent",
|
|
6504
|
+
wait_agent: "WaitAgent",
|
|
6505
|
+
send_input: "SendInput",
|
|
6506
|
+
close_agent: "CloseAgent",
|
|
6075
6507
|
task: "Task",
|
|
6076
6508
|
todo: "Todo",
|
|
6077
6509
|
question: "Questions",
|
|
@@ -6080,6 +6512,20 @@ function displayToolName(name) {
|
|
|
6080
6512
|
}
|
|
6081
6513
|
function toolHeader(tool) {
|
|
6082
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
|
+
}
|
|
6083
6529
|
const value = args.path ?? args.command ?? args.pattern ?? args.url ?? args.query;
|
|
6084
6530
|
return value ? `(${truncate(String(value).replace(/\n/g, " "), 64)})` : "";
|
|
6085
6531
|
}
|
|
@@ -6125,8 +6571,11 @@ function filetype(filePath) {
|
|
|
6125
6571
|
return ext ? map[ext] : undefined;
|
|
6126
6572
|
}
|
|
6127
6573
|
function summarizeToolResult(tool) {
|
|
6128
|
-
if (!isToolFinished(tool))
|
|
6129
|
-
|
|
6574
|
+
if (!isToolFinished(tool)) {
|
|
6575
|
+
if (tool.status === "running")
|
|
6576
|
+
return "running";
|
|
6577
|
+
return tool.streamingArgs ? "preparing" : "pending";
|
|
6578
|
+
}
|
|
6130
6579
|
if (tool.name === "question") {
|
|
6131
6580
|
if (isQuestionRejected(tool))
|
|
6132
6581
|
return "dismissed";
|
|
@@ -6137,14 +6586,47 @@ function summarizeToolResult(tool) {
|
|
|
6137
6586
|
if (tool.isError)
|
|
6138
6587
|
return truncate(result.split("\n").find(Boolean) || "error", 120);
|
|
6139
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 "";
|
|
6140
6592
|
if (tool.name === "edit")
|
|
6141
6593
|
return "patched file";
|
|
6142
6594
|
if (tool.name === "write")
|
|
6143
6595
|
return "wrote file";
|
|
6144
|
-
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"}`;
|
|
6145
6604
|
return lines ? `${lines} line${lines === 1 ? "" : "s"} output` : "done";
|
|
6605
|
+
}
|
|
6146
6606
|
return lines ? `${lines} line${lines === 1 ? "" : "s"}` : "done";
|
|
6147
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
|
+
}
|
|
6148
6630
|
function toolSummaryWithPreview(tool) {
|
|
6149
6631
|
const summary = ` ${summarizeToolResult(tool)}`;
|
|
6150
6632
|
const preview = toolPreview(tool);
|
|
@@ -6159,7 +6641,7 @@ function toolSummaryWithPreview(tool) {
|
|
|
6159
6641
|
function toolPreview(tool) {
|
|
6160
6642
|
if (!isToolFinished(tool) || tool.isError || !tool.result)
|
|
6161
6643
|
return undefined;
|
|
6162
|
-
if (tool.name !== "
|
|
6644
|
+
if (tool.name !== "glob")
|
|
6163
6645
|
return undefined;
|
|
6164
6646
|
const lines = tool.result
|
|
6165
6647
|
.replace(/\r\n/g, "\n")
|
|
@@ -6215,7 +6697,10 @@ function isToolFinished(tool) {
|
|
|
6215
6697
|
function assistantStatusLabel(message) {
|
|
6216
6698
|
if (message.status === "responding")
|
|
6217
6699
|
return "Responding...";
|
|
6218
|
-
|
|
6700
|
+
const elapsed = formatDuration(reasoningElapsedMs(message));
|
|
6701
|
+
if (message.streaming)
|
|
6702
|
+
return elapsed ? `Thinking ${elapsed}...` : "Thinking...";
|
|
6703
|
+
return elapsed ? `Thought for ${elapsed}` : "Thinking";
|
|
6219
6704
|
}
|
|
6220
6705
|
function buildContextGauge(percent, barWidth) {
|
|
6221
6706
|
const clamped = Math.max(0, Math.min(100, percent));
|
|
@@ -6246,9 +6731,69 @@ function formatContextRemaining(value) {
|
|
|
6246
6731
|
return `${(value / 1_000).toFixed(1)}K`;
|
|
6247
6732
|
return String(value);
|
|
6248
6733
|
}
|
|
6249
|
-
function
|
|
6250
|
-
const
|
|
6251
|
-
|
|
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(" · ")}`;
|
|
6252
6797
|
}
|
|
6253
6798
|
function truncate(value, max) {
|
|
6254
6799
|
return value.length > max ? value.slice(0, Math.max(1, max - 1)).trimEnd() + "…" : value;
|