@deepseek-harness-tui/dsh-tui 0.8.6 → 0.8.7
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 +13 -3
- package/bin/dsh-tui.js +269 -218
- package/cordis.patch.yml +48 -22
- package/lib/types/cc/figures.d.ts.map +1 -1
- package/lib/types/cc/figures.js +14 -13
- package/lib/types/cc/syntaxRuns.d.ts +16 -0
- package/lib/types/cc/syntaxRuns.d.ts.map +1 -0
- package/lib/types/cc/syntaxRuns.js +79 -0
- package/lib/types/commands.d.ts +14 -0
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +20 -1
- package/lib/types/components/Chat/SubagentMessage.d.ts +19 -0
- package/lib/types/components/Chat/SubagentMessage.d.ts.map +1 -0
- package/lib/types/components/Chat/SubagentMessage.js +75 -0
- package/lib/types/components/EffortInputBorder.d.ts.map +1 -1
- package/lib/types/components/EffortInputBorder.js +2 -2
- package/lib/types/components/EffortTierBadge.js +1 -1
- package/lib/types/components/HelpMenu.d.ts +8 -1
- package/lib/types/components/HelpMenu.d.ts.map +1 -1
- package/lib/types/components/HelpMenu.js +15 -4
- package/lib/types/components/LogoV2.d.ts +3 -1
- package/lib/types/components/LogoV2.d.ts.map +1 -1
- package/lib/types/components/LogoV2.js +2 -2
- package/lib/types/components/MessageList.d.ts +6 -2
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +17 -6
- package/lib/types/components/ModelPicker.js +2 -2
- package/lib/types/components/PromptInput.d.ts +1 -1
- package/lib/types/components/PromptInput.d.ts.map +1 -1
- package/lib/types/components/PromptInput.js +75 -15
- package/lib/types/components/SkillsPicker.js +2 -2
- package/lib/types/components/SplitDiffView.d.ts +1 -13
- package/lib/types/components/SplitDiffView.d.ts.map +1 -1
- package/lib/types/components/SplitDiffView.js +6 -105
- package/lib/types/components/SubagentCard.d.ts +9 -0
- package/lib/types/components/SubagentCard.d.ts.map +1 -0
- package/lib/types/components/SubagentCard.js +26 -0
- package/lib/types/components/SubagentDashboard.d.ts +13 -0
- package/lib/types/components/SubagentDashboard.d.ts.map +1 -0
- package/lib/types/components/SubagentDashboard.js +50 -0
- package/lib/types/components/SubagentDetailScene.d.ts +16 -0
- package/lib/types/components/SubagentDetailScene.d.ts.map +1 -0
- package/lib/types/components/SubagentDetailScene.js +141 -0
- package/lib/types/components/SyntaxText.d.ts +15 -0
- package/lib/types/components/SyntaxText.d.ts.map +1 -0
- package/lib/types/components/SyntaxText.js +34 -0
- package/lib/types/components/ThinkingToggle.d.ts +3 -10
- package/lib/types/components/ThinkingToggle.d.ts.map +1 -1
- package/lib/types/components/ThinkingToggle.js +4 -9
- package/lib/types/components/design-system/ListItem.d.ts.map +1 -1
- package/lib/types/components/design-system/ListItem.js +1 -1
- package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantThinkingMessage.js +18 -7
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts +2 -0
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantToolUseMessage.js +42 -6
- package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
- package/lib/types/components/messages/UserPromptMessage.js +15 -2
- package/lib/types/dsh-adapter/channel.d.ts +53 -2
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +294 -7
- package/lib/types/dsh-adapter/contract.d.ts +27 -4
- package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
- package/lib/types/dsh-adapter/contract.js +56 -10
- package/lib/types/dsh-adapter/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.js +9 -3
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +38 -0
- package/lib/types/dsh-adapter/shortcuts.js +3 -3
- package/lib/types/dsh-adapter/subagents.d.ts +87 -0
- package/lib/types/dsh-adapter/subagents.d.ts.map +1 -0
- package/lib/types/dsh-adapter/subagents.js +204 -0
- package/lib/types/herdr.d.ts +28 -0
- package/lib/types/herdr.d.ts.map +1 -0
- package/lib/types/herdr.js +133 -0
- package/lib/types/i18n.d.ts +215 -31
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +65 -14
- package/lib/types/ink/ink.d.ts +4 -0
- package/lib/types/ink/ink.d.ts.map +1 -1
- package/lib/types/ink/ink.js +46 -15
- package/lib/types/minimalMode.d.ts +13 -0
- package/lib/types/minimalMode.d.ts.map +1 -0
- package/lib/types/minimalMode.js +17 -0
- package/lib/types/screens/Chat.d.ts +1 -1
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +94 -43
- package/lib/types/screens/StatusLine.d.ts.map +1 -1
- package/lib/types/screens/StatusLine.js +6 -2
- package/lib/types/screens/TrajectoryScene.d.ts.map +1 -1
- package/lib/types/screens/TrajectoryScene.js +4 -2
- package/lib/types/theme.d.ts +8 -0
- package/lib/types/theme.d.ts.map +1 -1
- package/lib/types/theme.js +38 -14
- package/lib/types/tips.js +3 -3
- package/lib/types/update.d.ts +18 -0
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +73 -2
- package/lib/types/utils/execFileNoThrow.d.ts +3 -20
- package/lib/types/utils/execFileNoThrow.d.ts.map +1 -1
- package/lib/types/utils/execFileNoThrow.js +53 -32
- package/lib/types/utils/externalEditor.d.ts +12 -11
- package/lib/types/utils/externalEditor.d.ts.map +1 -1
- package/lib/types/utils/externalEditor.js +11 -11
- package/package.json +64 -58
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Box, Text, useInput, ScrollBox, useTerminalSize } from '../ui.js';
|
|
4
|
+
import { t } from '../i18n.js';
|
|
5
|
+
import { Divider } from './design-system/Divider.js';
|
|
6
|
+
import { isPlainReturnInput } from '../utils/modifiers.js';
|
|
7
|
+
import { toolNameColor } from './messages/AssistantToolUseMessage.js';
|
|
8
|
+
import { getCliHighlightPromise } from '../cc/cliHighlight.js';
|
|
9
|
+
import { isMinimalMode } from '../minimalMode.js';
|
|
10
|
+
function formatDuration(ms) {
|
|
11
|
+
if (ms < 1000)
|
|
12
|
+
return `${ms}ms`;
|
|
13
|
+
if (ms < 60000)
|
|
14
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
15
|
+
const min = Math.floor(ms / 60000);
|
|
16
|
+
const sec = Math.floor((ms % 60000) / 1000);
|
|
17
|
+
return `${min}m${sec}s`;
|
|
18
|
+
}
|
|
19
|
+
function formatTimestamp(ts) {
|
|
20
|
+
return new Date(ts).toLocaleTimeString();
|
|
21
|
+
}
|
|
22
|
+
function statusGlyph(status) {
|
|
23
|
+
const minimal = isMinimalMode();
|
|
24
|
+
if (status === 'completed')
|
|
25
|
+
return { glyph: minimal ? '✓' : '🟢', color: minimal ? undefined : 'success', label: 'done' };
|
|
26
|
+
if (status === 'failed')
|
|
27
|
+
return { glyph: minimal ? '×' : '🔴', color: minimal ? undefined : 'error', label: 'failed' };
|
|
28
|
+
if (status === 'cancelled')
|
|
29
|
+
return { glyph: minimal ? '×' : '🔴', color: minimal ? undefined : 'error', label: 'cancelled' };
|
|
30
|
+
return { glyph: minimal ? '·' : '🟡', color: minimal ? undefined : 'warning', label: 'running' };
|
|
31
|
+
}
|
|
32
|
+
const PAGES = ['summary', 'output', 'tools'];
|
|
33
|
+
/** One label/value row of the summary stats card. */
|
|
34
|
+
function StatRow({ label, children }) {
|
|
35
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 14, flexShrink: 0, children: _jsx(Text, { dimColor: true, children: label }) }), _jsx(Box, { flexDirection: "row", flexGrow: 1, children: children })] }));
|
|
36
|
+
}
|
|
37
|
+
/** Two-column key/value stats grid (Kimi Code settled summary style). */
|
|
38
|
+
function StatGrid({ subagent, totalTokens, elapsed, statusLabel, statusColor }) {
|
|
39
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(StatRow, { label: t('subagent-status-label'), children: _jsx(Text, { color: statusColor, children: statusLabel }) }), _jsx(StatRow, { label: t('subagent-model'), children: _jsx(Text, { children: subagent.model ?? subagent.provider ?? 'default' }) }), _jsx(StatRow, { label: t('subagent-duration'), children: _jsx(Text, { children: formatDuration(elapsed) }) }), _jsx(StatRow, { label: "tokens", children: _jsxs(Text, { children: [totalTokens || '—', subagent.tokens?.input !== undefined ? ` (in ${subagent.tokens.input} · out ${subagent.tokens.output ?? 0})` : ''] }) }), _jsx(StatRow, { label: t('subagent-tools'), children: _jsx(Text, { children: subagent.toolCalls.length }) }), _jsx(StatRow, { label: t('subagent-started'), children: _jsx(Text, { children: formatTimestamp(subagent.startedAt) }) }), subagent.completedAt !== undefined && (_jsx(StatRow, { label: t('subagent-completed'), children: _jsx(Text, { children: formatTimestamp(subagent.completedAt) }) }))] }));
|
|
40
|
+
}
|
|
41
|
+
/** Tool args line: JSON-looking args get cli-highlight syntax colors (loaded
|
|
42
|
+
* lazily through the shared promise); anything else stays a dim flat line. */
|
|
43
|
+
function JsonArgsText({ raw }) {
|
|
44
|
+
const flat = raw.replace(/\s+/g, ' ').trim();
|
|
45
|
+
const json = flat.startsWith('{') || flat.startsWith('[');
|
|
46
|
+
const [highlighted, setHighlighted] = React.useState(null);
|
|
47
|
+
React.useEffect(() => {
|
|
48
|
+
if (!json)
|
|
49
|
+
return;
|
|
50
|
+
let alive = true;
|
|
51
|
+
void getCliHighlightPromise().then(cli => {
|
|
52
|
+
if (!alive || cli === null)
|
|
53
|
+
return;
|
|
54
|
+
try {
|
|
55
|
+
setHighlighted(cli.highlight(flat, { language: 'json' }));
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Not parseable JSON after all — keep the dim fallback.
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
return () => { alive = false; };
|
|
62
|
+
}, [flat, json]);
|
|
63
|
+
if (json && highlighted !== null)
|
|
64
|
+
return _jsx(Text, { wrap: "wrap", children: highlighted });
|
|
65
|
+
return _jsx(Text, { dimColor: true, wrap: "wrap", children: flat });
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* SubagentDetailScene — full-screen paged detail view for one subagent.
|
|
69
|
+
* Header block (identity + stats) stays fixed; the body pages through
|
|
70
|
+
* 摘要 / 输出 / 工具 with ←/→. Follow-up delivery was removed: the official
|
|
71
|
+
* seam only accepts continuable children, and one-shot spawn children are
|
|
72
|
+
* disposed at settlement, so the affordance would be a dead control.
|
|
73
|
+
*/
|
|
74
|
+
export function SubagentDetailScene({ subagent, onBack, onInterrupt, }) {
|
|
75
|
+
const scrollRef = React.useRef(null);
|
|
76
|
+
const { rows, columns } = useTerminalSize();
|
|
77
|
+
const [page, setPage] = React.useState('summary');
|
|
78
|
+
const isRunning = subagent.status === 'running' || subagent.status === 'starting';
|
|
79
|
+
const elapsed = subagent.completedAt ? subagent.completedAt - subagent.startedAt : Date.now() - subagent.startedAt;
|
|
80
|
+
const info = statusGlyph(subagent.status);
|
|
81
|
+
const totalTokens = subagent.tokens?.total ?? ((subagent.tokens?.input ?? 0) + (subagent.tokens?.output ?? 0) || 0);
|
|
82
|
+
const pageIndex = PAGES.indexOf(page);
|
|
83
|
+
const turnPage = (delta) => {
|
|
84
|
+
const next = (pageIndex + delta + PAGES.length) % PAGES.length;
|
|
85
|
+
setPage(PAGES[next]);
|
|
86
|
+
scrollRef.current?.scrollTo?.(0);
|
|
87
|
+
};
|
|
88
|
+
// tail -f: while the subagent runs and the output page is showing, follow
|
|
89
|
+
// the newest streamed line. Page switches or settlement stop the follow so
|
|
90
|
+
// manual ↑ scrolling wins.
|
|
91
|
+
const outputLength = subagent.outputEvents.length;
|
|
92
|
+
React.useEffect(() => {
|
|
93
|
+
if (page !== 'output' || !isRunning)
|
|
94
|
+
return;
|
|
95
|
+
scrollRef.current?.scrollToBottom();
|
|
96
|
+
}, [page, isRunning, outputLength]);
|
|
97
|
+
useInput((input, key, event) => {
|
|
98
|
+
if (key.escape || (key.ctrl && input === 'c')) {
|
|
99
|
+
event.stopImmediatePropagation();
|
|
100
|
+
onBack();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (key.leftArrow) {
|
|
104
|
+
event.stopImmediatePropagation();
|
|
105
|
+
turnPage(-1);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (key.rightArrow) {
|
|
109
|
+
event.stopImmediatePropagation();
|
|
110
|
+
turnPage(1);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (key.upArrow) {
|
|
114
|
+
event.stopImmediatePropagation();
|
|
115
|
+
scrollRef.current?.scrollBy(-3);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (key.downArrow) {
|
|
119
|
+
event.stopImmediatePropagation();
|
|
120
|
+
scrollRef.current?.scrollBy(3);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (input.toLowerCase() === 'x' && isRunning && onInterrupt) {
|
|
124
|
+
event.stopImmediatePropagation();
|
|
125
|
+
onInterrupt(subagent.agentId);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (isPlainReturnInput(input, key)) {
|
|
129
|
+
event.stopImmediatePropagation();
|
|
130
|
+
onBack();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
event.stopImmediatePropagation();
|
|
134
|
+
});
|
|
135
|
+
const tab = (name, label) => {
|
|
136
|
+
const active = page === name;
|
|
137
|
+
return (_jsxs(React.Fragment, { children: [_jsx(Text, { color: active ? 'claude' : undefined, bold: active, inverse: active, children: ` ${label} ` }), _jsx(Text, { dimColor: true, children: name === PAGES[PAGES.length - 1] ? '' : '│' })] }, name));
|
|
138
|
+
};
|
|
139
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [_jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Text, { color: info.color, bold: true, children: info.glyph }), _jsx(Text, { bold: true, children: `${t('subagent-card-prefix')}${subagent.description}` }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { color: info.color, children: info.label })] }), _jsxs(Text, { children: [_jsx(Text, { children: subagent.model ?? subagent.provider ?? 'default' }), _jsx(Text, { dimColor: true, children: ` · ${formatDuration(elapsed)} · ${totalTokens || '—'} tok · ${subagent.toolCalls.length} tools` })] }), _jsxs(Text, { dimColor: true, children: [`${t('subagent-started')} ${formatTimestamp(subagent.startedAt)}`
|
|
140
|
+
+ (subagent.completedAt ? ` · ${t('subagent-completed')} ${formatTimestamp(subagent.completedAt)}` : ''), ` · id ${subagent.agentId}`] }), subagent.error && (_jsx(Box, { marginTop: 0, children: _jsx(Text, { color: "error", wrap: "wrap", children: `${t('subagent-error-label')}: ${subagent.error}` }) })), _jsxs(Box, { flexDirection: "row", gap: 0, marginTop: 1, children: [tab('summary', t('subagent-tab-summary')), tab('output', t('subagent-output-label')), tab('tools', t('subagent-tools')), _jsx(Text, { dimColor: true, children: ` ${pageIndex + 1}/${PAGES.length}` })] }), _jsx(Text, { dimColor: true, children: '─'.repeat(Math.max(20, Math.min(72, columns - 6))) }), _jsx(Box, { flexDirection: "column", paddingX: 1, maxHeight: Math.max(10, rows - 14), children: _jsxs(ScrollBox, { ref: scrollRef, flexDirection: "column", flexGrow: 1, children: [page === 'summary' && (_jsxs(Box, { flexDirection: "column", children: [_jsx(StatGrid, { subagent: subagent, totalTokens: totalTokens, elapsed: elapsed, statusLabel: info.label, statusColor: info.color }), subagent.summary && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { dimColor: true, bold: true, children: '─ summary ' }), _jsx(Text, { wrap: "wrap", children: subagent.summary })] })), !subagent.summary && (_jsx(Text, { dimColor: true, children: isRunning ? t('subagent-no-output') : t('subagent-no-summary') }))] })), page === 'output' && (subagent.outputEvents.length === 0 && subagent.output.length === 0 ? (_jsx(Text, { dimColor: true, children: t('subagent-no-output') })) : (subagent.outputEvents.map((line, index) => (_jsxs(Text, { wrap: "wrap", dimColor: line.kind === 'thinking' || line.kind === 'system', color: line.kind === 'error' ? 'error' : undefined, children: [line.kind === 'thinking' ? ' ⌁ ' : ' ', line.text, !line.settled && isRunning ? ' ▍' : ''] }, index))))), page === 'tools' && (subagent.toolCalls.length === 0 ? (_jsx(Text, { dimColor: true, children: t('subagent-no-tools') })) : (subagent.toolCalls.map((tool, index) => (_jsxs(Box, { flexDirection: "column", marginTop: index === 0 ? 0 : 1, children: [_jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Text, { color: tool.status === 'failed' ? 'error' : tool.status === 'running' ? 'warning' : 'success', children: tool.status === 'running' ? '·' : tool.status === 'failed' ? '×' : '✓' }), _jsx(Text, { color: toolNameColor(tool.name), children: tool.name }), tool.endedAt && _jsx(Text, { dimColor: true, children: formatDuration(tool.endedAt - tool.startedAt) })] }), tool.argsPreview && (_jsx(Box, { flexDirection: "row", paddingLeft: 2, children: _jsx(JsonArgsText, { raw: tool.argsPreview }) })), tool.resultPreview && (_jsx(Box, { flexDirection: "row", paddingLeft: 2, children: _jsx(Text, { dimColor: true, wrap: "wrap", children: `⎿ ${tool.resultPreview}` }) })), tool.error && (_jsx(Box, { flexDirection: "row", paddingLeft: 2, children: _jsx(Text, { color: "error", wrap: "wrap", children: tool.error }) }))] }, tool.id ?? index)))))] }) }), _jsx(Divider, { color: "subtle", title: "", padding: 4 }), _jsx(Box, { marginTop: 0, children: _jsxs(Text, { dimColor: true, children: [`←/→ ${t('subagent-hint-page')} · ↑/↓ ${t('subagent-hint-scroll')}`, isRunning && onInterrupt ? ' · X interrupt' : '', ` · Esc ${t('subagent-hint-back')}`] }) })] }));
|
|
141
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Color } from '../ink/styles.js';
|
|
3
|
+
type Props = {
|
|
4
|
+
readonly text: string;
|
|
5
|
+
readonly language?: string;
|
|
6
|
+
/** Complete payload used for lexing; text is the visible line. */
|
|
7
|
+
readonly sourceText?: string;
|
|
8
|
+
readonly lineIndex?: number;
|
|
9
|
+
readonly color?: Color;
|
|
10
|
+
readonly dimColor?: boolean;
|
|
11
|
+
};
|
|
12
|
+
/** Async nested Text renderer. Lexes the complete payload before splitting. */
|
|
13
|
+
export declare function SyntaxText({ text, language, sourceText, lineIndex, color, dimColor }: Props): React.ReactNode;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=SyntaxText.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SyntaxText.d.ts","sourceRoot":"","sources":["../../../src/components/SyntaxText.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAM7C,KAAK,KAAK,GAAG;IACX,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,kEAAkE;IAClE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAA;IACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAC5B,CAAA;AAED,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAiB,EAAE,SAAa,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,KAAK,GAAG,KAAK,CAAC,SAAS,CAgCxH"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Text } from '../ui.js';
|
|
4
|
+
import { getCliHighlightPromise } from '../cc/cliHighlight.js';
|
|
5
|
+
import { highlightLines, syntaxThemeSignature } from '../cc/syntaxRuns.js';
|
|
6
|
+
import { getTheme } from '../theme.js';
|
|
7
|
+
import { useTheme } from './design-system/ThemeProvider.js';
|
|
8
|
+
/** Async nested Text renderer. Lexes the complete payload before splitting. */
|
|
9
|
+
export function SyntaxText({ text, language, sourceText = text, lineIndex = 0, color, dimColor }) {
|
|
10
|
+
const [highlighter, setHighlighter] = React.useState(null);
|
|
11
|
+
const [themeName] = useTheme();
|
|
12
|
+
React.useEffect(() => {
|
|
13
|
+
let mounted = true;
|
|
14
|
+
void getCliHighlightPromise().then(value => { if (mounted)
|
|
15
|
+
setHighlighter(value); });
|
|
16
|
+
return () => { mounted = false; };
|
|
17
|
+
}, []);
|
|
18
|
+
const theme = React.useMemo(() => getTheme(themeName), [themeName]);
|
|
19
|
+
const themeSig = React.useMemo(() => syntaxThemeSignature(theme), [theme]);
|
|
20
|
+
const syntax = React.useMemo(() => highlightLines(sourceText, language, highlighter, theme, themeSig), [sourceText, language, highlighter, theme, themeSig]);
|
|
21
|
+
const runs = syntax?.[lineIndex];
|
|
22
|
+
if (runs === undefined || color !== undefined || dimColor) {
|
|
23
|
+
return _jsx(Text, { color: color, dimColor: dimColor, children: text === '' ? ' ' : text });
|
|
24
|
+
}
|
|
25
|
+
let remaining = text.length;
|
|
26
|
+
const visibleRuns = runs.flatMap(run => {
|
|
27
|
+
if (remaining <= 0)
|
|
28
|
+
return [];
|
|
29
|
+
const visible = run.text.slice(0, remaining);
|
|
30
|
+
remaining -= visible.length;
|
|
31
|
+
return visible === '' ? [] : [{ ...run, text: visible }];
|
|
32
|
+
});
|
|
33
|
+
return (_jsx(Text, { children: visibleRuns.length === 0 ? text : visibleRuns.map((run, index) => (_jsx(Text, { color: run.color, children: run.text }, index))) }));
|
|
34
|
+
}
|
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
/**
|
|
3
|
-
* The `/thinking` dialog
|
|
4
|
-
*
|
|
5
|
-
* (with CC's option descriptions), and the Enter/Esc hint line.
|
|
6
|
-
*
|
|
7
|
-
* When `confirmationPending` is set (mid-conversation toggle), the select is
|
|
8
|
-
* replaced by CC's yellow warning block and the hint line becomes
|
|
9
|
-
* Enter confirm / Esc cancel; keyboard handling lives in the caller (Chat).
|
|
3
|
+
* The `/thinking` display dialog: a permission-colored Pane with a bold
|
|
4
|
+
* title, the Shown/Hidden select, and the Enter/Esc hint line.
|
|
10
5
|
*/
|
|
11
|
-
export declare function ThinkingToggle({ currentValue, focusIndex,
|
|
6
|
+
export declare function ThinkingToggle({ currentValue, focusIndex, }: {
|
|
12
7
|
currentValue: boolean;
|
|
13
8
|
focusIndex: number;
|
|
14
|
-
/** Set while a mid-conversation toggle awaits Enter confirmation. */
|
|
15
|
-
confirmationPending: boolean | null;
|
|
16
9
|
}): React.ReactNode;
|
|
17
10
|
//# sourceMappingURL=ThinkingToggle.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThinkingToggle.d.ts","sourceRoot":"","sources":["../../../src/components/ThinkingToggle.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAOzB
|
|
1
|
+
{"version":3,"file":"ThinkingToggle.d.ts","sourceRoot":"","sources":["../../../src/components/ThinkingToggle.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAOzB;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAC7B,YAAY,EACZ,UAAU,GACX,EAAE;IACD,YAAY,EAAE,OAAO,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;CACnB,GAAG,KAAK,CAAC,SAAS,CAsClB"}
|
|
@@ -5,15 +5,10 @@ import { Pane } from './design-system/Pane.js';
|
|
|
5
5
|
import { Select } from './Select.js';
|
|
6
6
|
import { HintLine } from './design-system/HintLine.js';
|
|
7
7
|
/**
|
|
8
|
-
* The `/thinking` dialog
|
|
9
|
-
*
|
|
10
|
-
* (with CC's option descriptions), and the Enter/Esc hint line.
|
|
11
|
-
*
|
|
12
|
-
* When `confirmationPending` is set (mid-conversation toggle), the select is
|
|
13
|
-
* replaced by CC's yellow warning block and the hint line becomes
|
|
14
|
-
* Enter confirm / Esc cancel; keyboard handling lives in the caller (Chat).
|
|
8
|
+
* The `/thinking` display dialog: a permission-colored Pane with a bold
|
|
9
|
+
* title, the Shown/Hidden select, and the Enter/Esc hint line.
|
|
15
10
|
*/
|
|
16
|
-
export function ThinkingToggle({ currentValue, focusIndex,
|
|
11
|
+
export function ThinkingToggle({ currentValue, focusIndex, }) {
|
|
17
12
|
const options = [
|
|
18
13
|
{
|
|
19
14
|
value: 'true',
|
|
@@ -26,5 +21,5 @@ export function ThinkingToggle({ currentValue, focusIndex, confirmationPending,
|
|
|
26
21
|
description: t('thinking-disabled-desc'),
|
|
27
22
|
},
|
|
28
23
|
];
|
|
29
|
-
return (_jsxs(Pane, { color: "permission", children: [_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsx(Text, { color: "remember", bold: true, children: t('thinking-title') }), _jsx(Text, { dimColor: true, children: t('thinking-subtitle') })] }),
|
|
24
|
+
return (_jsxs(Pane, { color: "permission", children: [_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsx(Text, { color: "remember", bold: true, children: t('thinking-title') }), _jsx(Text, { dimColor: true, children: t('thinking-subtitle') })] }), _jsx(Box, { flexDirection: "column", marginBottom: 1, children: _jsx(Select, { options: options, focusIndex: focusIndex, selectedValue: currentValue ? 'true' : 'false', visibleOptionCount: 2 }) })] }), _jsx(Text, { dimColor: true, italic: true, children: _jsx(HintLine, { text: t('hint-confirm-exit') }) })] }));
|
|
30
25
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ListItem.d.ts","sourceRoot":"","sources":["../../../../src/components/design-system/ListItem.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAK7C,MAAM,MAAM,aAAa,GAAG;IAC1B;qDACiD;IACjD,SAAS,EAAE,OAAO,CAAA;IAClB;uDACmD;IACnD,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,4CAA4C;IAC5C,QAAQ,EAAE,SAAS,CAAA;IACnB,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,qEAAqE;IACrE,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,oEAAoE;IACpE,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,yEAAyE;IACzE,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,EACvB,SAAS,EACT,UAAkB,EAClB,QAAQ,EACR,WAAW,EACX,cAAc,EACd,YAAY,EACZ,MAAa,EACb,QAAgB,EAChB,aAAa,GACd,EAAE,aAAa,GAAG,KAAK,CAAC,SAAS,
|
|
1
|
+
{"version":3,"file":"ListItem.d.ts","sourceRoot":"","sources":["../../../../src/components/design-system/ListItem.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAK7C,MAAM,MAAM,aAAa,GAAG;IAC1B;qDACiD;IACjD,SAAS,EAAE,OAAO,CAAA;IAClB;uDACmD;IACnD,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,4CAA4C;IAC5C,QAAQ,EAAE,SAAS,CAAA;IACnB,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,qEAAqE;IACrE,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,oEAAoE;IACpE,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,yEAAyE;IACzE,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,EACvB,SAAS,EACT,UAAkB,EAClB,QAAQ,EACR,WAAW,EACX,cAAc,EACd,YAAY,EACZ,MAAa,EACb,QAAgB,EAChB,aAAa,GACd,EAAE,aAAa,GAAG,KAAK,CAAC,SAAS,CAwEjC"}
|
|
@@ -54,7 +54,7 @@ export function ListItem({ isFocused, isSelected = false, children, description,
|
|
|
54
54
|
// 非 Fragment 的 element(如调用方自己的 <Text>)保留原样——其子树高度
|
|
55
55
|
// 由调用方负责。
|
|
56
56
|
const flatChildren = flattenDeep(children);
|
|
57
|
-
return (_jsxs(Box, { ref: cursorRef, flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", gap: 1, children: [renderIndicator(), styled ? (_jsx(Text, { color: getTextColor(), dimColor: disabled, wrap: "truncate", children: flatChildren })) : (flatChildren), isSelected && !disabled && _jsx(Text, { color: "success", children: TICK })] }), description && (_jsx(Box, { paddingLeft: 2, children: _jsx(Text, { color: "inactive", wrap: "truncate", children: flattenLine(description) }) }))] }));
|
|
57
|
+
return (_jsxs(Box, { ref: cursorRef, flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", gap: 1, height: 1, flexShrink: 0, overflow: "hidden", width: "100%", children: [renderIndicator(), styled ? (_jsx(Text, { color: getTextColor(), dimColor: disabled, wrap: "truncate-end", children: flatChildren })) : (flatChildren), isSelected && !disabled && _jsx(Text, { color: "success", children: TICK })] }), description && (_jsx(Box, { paddingLeft: 2, children: _jsx(Text, { color: "inactive", wrap: "truncate", children: flattenLine(description) }) }))] }));
|
|
58
58
|
}
|
|
59
59
|
/** 单行化:内嵌换行折叠为空格(行尾/行首换行随之消除)。 */
|
|
60
60
|
function flattenLine(s) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssistantThinkingMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/messages/AssistantThinkingMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"AssistantThinkingMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/messages/AssistantThinkingMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAuBzB,KAAK,KAAK,GAAG;IACX,QAAQ,EAAE,MAAM,CAAA;IAChB,4DAA4D;IAC5D,SAAS,EAAE,OAAO,CAAA;IAClB,2EAA2E;IAC3E,OAAO,EAAE,OAAO,CAAA;IAChB;;4CAEwC;IACxC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;8EAE0E;IAC1E,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,wCAAwC;IACxC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,OAAO,CAAC,IAAI,IAAI,CAAA;CACjB,CAAA;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,QAAQ,EACR,SAAS,EACT,OAAO,EACP,SAAiB,EACjB,OAAe,EACf,UAAU,EACV,UAAkB,EAClB,OAAO,GACR,EAAE,KAAK,GAAG,KAAK,CAAC,SAAS,CAwHzB"}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React from 'react';
|
|
3
|
+
import chalk from 'chalk';
|
|
3
4
|
import { Box, Text } from '../../ui.js';
|
|
4
5
|
import { t } from '../../i18n.js';
|
|
5
6
|
import { StreamingMarkdown } from '../StreamingMarkdown.js';
|
|
6
7
|
import { formatDuration } from '../../cc/format.js';
|
|
7
8
|
import { THINKING_SPINNER_FRAMES, THINKING_SPINNER_INTERVAL_MS, THINKING_SETTLED_MARKER, } from '../../cc/figures.js';
|
|
9
|
+
import { BRAND, ICE } from '../shimmer.js';
|
|
10
|
+
import { interpolateColor } from '../Spinner/spinnerUtils.js';
|
|
11
|
+
import { isMinimalMode } from '../../minimalMode.js';
|
|
8
12
|
/** Preview body rows — a FIXED row count (kimicode-style constant-height
|
|
9
13
|
* ticker). Ink's truncate slices the whole string across newlines as one
|
|
10
14
|
* logical line, so a single joined Text collapses to 1-2 rows whenever
|
|
@@ -33,12 +37,19 @@ export function AssistantThinkingMessage({ thinking, addMargin, verbose, streami
|
|
|
33
37
|
const interval = setInterval(() => setFrame(f => f + 1), THINKING_SPINNER_INTERVAL_MS);
|
|
34
38
|
return () => clearInterval(interval);
|
|
35
39
|
}, [streaming]);
|
|
36
|
-
const marker = streaming
|
|
37
|
-
? THINKING_SPINNER_FRAMES[frame % THINKING_SPINNER_FRAMES.length]
|
|
38
|
-
: THINKING_SETTLED_MARKER;
|
|
39
40
|
const duration = durationMs !== undefined && durationMs >= 1000
|
|
40
41
|
? ` · ${formatDuration(durationMs)}`
|
|
41
42
|
: '';
|
|
43
|
+
// Kimi Code style blue pulse: the streaming glyph breathes along the
|
|
44
|
+
// header's brand→ice ladder, one sine period per ~7 frames (≈0.56s) —
|
|
45
|
+
// lively without strobing. Minimal mode drops the color (plain glyph);
|
|
46
|
+
// settled always keeps the plain dim anchor.
|
|
47
|
+
const label = `${t('thinking-label')}${duration}${streaming ? '…' : ` ${t('hint-expand-ctrl-o')}`}`;
|
|
48
|
+
const minimal = isMinimalMode();
|
|
49
|
+
const pulse = (Math.sin(frame * 0.9) + 1) / 2;
|
|
50
|
+
const pulseColor = interpolateColor(BRAND, ICE, pulse);
|
|
51
|
+
const frameText = THINKING_SPINNER_FRAMES[frame % THINKING_SPINNER_FRAMES.length];
|
|
52
|
+
const header = streaming ? (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { children: minimal ? frameText : chalk.rgb(pulseColor.r, pulseColor.g, pulseColor.b).bold(frameText) }), _jsx(Text, { dimColor: true, italic: true, children: ` ${label}` })] })) : (_jsx(Text, { dimColor: true, italic: true, children: `${minimal ? '*' : THINKING_SETTLED_MARKER} ${label}` }));
|
|
42
53
|
if (preview) {
|
|
43
54
|
// Live ticker: the model's last few reasoning lines, dimmed, one Text
|
|
44
55
|
// per row so each truncates to the width independently, padded to a
|
|
@@ -53,10 +64,10 @@ export function AssistantThinkingMessage({ thinking, addMargin, verbose, streami
|
|
|
53
64
|
// Pad with single spaces — an empty-string Text renders with zero
|
|
54
65
|
// height in ink, so '' padding would not hold the row open.
|
|
55
66
|
const rows = Array.from({ length: PREVIEW_ROWS }, (_, i) => visible[i] ?? ' ');
|
|
56
|
-
return (_jsxs(Box, { flexDirection: "column", marginTop: addMargin ? 1 : 0, backgroundColor: isSelected ? 'messageActionsBackground' : undefined, onClick: onClick, children: [
|
|
67
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: addMargin ? 1 : 0, backgroundColor: isSelected ? 'messageActionsBackground' : undefined, onClick: onClick, children: [header, _jsx(Box, { flexDirection: "column", paddingLeft: 2, height: PREVIEW_ROWS, flexShrink: 0, overflow: "hidden", children: rows.map((line, i) => (_jsxs(Box, { flexDirection: "row", height: 1, children: [_jsx(Text, { dimColor: true, italic: true, children: '│ ' }), _jsx(Box, { flexDirection: "row", flexGrow: 1, children: _jsx(Text, { dimColor: true, italic: true, wrap: i === rows.length - 1 ? 'truncate-start' : 'truncate', children: i === 0 && clipped ? `…${line}` : line }) })] }, i))) })] }));
|
|
57
68
|
}
|
|
58
69
|
if (!verbose) {
|
|
59
|
-
return (_jsx(Box, { marginTop: addMargin ? 1 : 0, backgroundColor: isSelected ? 'messageActionsBackground' : undefined, onClick: onClick, children:
|
|
70
|
+
return (_jsx(Box, { marginTop: addMargin ? 1 : 0, backgroundColor: isSelected ? 'messageActionsBackground' : undefined, onClick: onClick, children: header }));
|
|
60
71
|
}
|
|
61
|
-
return (_jsxs(Box, { flexDirection: "column", gap: 1, marginTop: addMargin ? 1 : 0, width: "100%", backgroundColor: isSelected ? 'messageActionsBackground' : undefined, onClick: onClick, children: [
|
|
72
|
+
return (_jsxs(Box, { flexDirection: "column", gap: 1, marginTop: addMargin ? 1 : 0, width: "100%", backgroundColor: isSelected ? 'messageActionsBackground' : undefined, onClick: onClick, children: [header, _jsx(Box, { paddingLeft: 2, children: _jsx(StreamingMarkdown, { dimColor: true, children: thinking }) })] }));
|
|
62
73
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { ToolRow } from '../../dsh-adapter/channel.js';
|
|
3
3
|
import type { ToolBackground } from '../../tuiDisplayPrefs.js';
|
|
4
|
+
import type { Theme } from '../../theme.js';
|
|
4
5
|
type Props = {
|
|
5
6
|
tool: ToolRow;
|
|
6
7
|
/** Adds the top margin between messages (CC: addMargin). */
|
|
@@ -25,6 +26,7 @@ type Props = {
|
|
|
25
26
|
/** Background treatment for the ordinary, unselected tool card surface. */
|
|
26
27
|
toolBackground?: ToolBackground;
|
|
27
28
|
};
|
|
29
|
+
export declare function toolNameColor(raw: string): keyof Theme;
|
|
28
30
|
/**
|
|
29
31
|
* Tool-call card: `● Edit /path` header with a blinking status dot, then the
|
|
30
32
|
* structured body under a ` ⎿ ` gutter — diff hunks in red/green, terminal
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssistantToolUseMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/messages/AssistantToolUseMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"AssistantToolUseMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/messages/AssistantToolUseMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAKzB,OAAO,KAAK,EAA8C,OAAO,EAAE,MAAM,8BAA8B,CAAA;AAKvG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAE3C,KAAK,KAAK,GAAG;IACX,IAAI,EAAE,OAAO,CAAA;IACb,4DAA4D;IAC5D,SAAS,EAAE,OAAO,CAAA;IAClB,iEAAiE;IACjE,OAAO,EAAE,OAAO,CAAA;IAChB,wCAAwC;IACxC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,sEAAsE;IACtE,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;IACzC,2EAA2E;IAC3E,cAAc,CAAC,EAAE,cAAc,CAAA;CAChC,CAAA;AAgFD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,KAAK,CAKtD;AAwKD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,EACtC,IAAI,EACJ,SAAS,EACT,OAAO,EACP,UAAkB,EAClB,UAAkB,EAClB,QAAQ,EACR,UAAmB,EACnB,cAAuB,GACxB,EAAE,KAAK,GAAG,KAAK,CAAC,SAAS,CAmKzB"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { extname } from 'node:path';
|
|
2
3
|
import { Box, Text, useTerminalSize } from '../../ui.js';
|
|
3
4
|
import { stringWidth } from '../../ink/stringWidth.js';
|
|
4
5
|
import { useAnimationFrame } from '../../ink/hooks/use-animation-frame.js';
|
|
5
6
|
import { ToolUseLoader } from '../ToolUseLoader.js';
|
|
6
7
|
import { SplitDiffView } from '../SplitDiffView.js';
|
|
8
|
+
import { SyntaxText } from '../SyntaxText.js';
|
|
7
9
|
import { formatDuration } from '../../cc/format.js';
|
|
8
10
|
/** Tool display names: DSH emits lowercase tool ids (`bash`); Claude Code
|
|
9
11
|
* shows capitalized names (`Bash`). Map the common ones, fall back to the
|
|
@@ -28,6 +30,33 @@ function displayName(name) {
|
|
|
28
30
|
return name;
|
|
29
31
|
return name[0].toUpperCase() + name.slice(1);
|
|
30
32
|
}
|
|
33
|
+
function parseJsonArgs(args) {
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(args);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function jsonArgsLanguage(args) {
|
|
42
|
+
return parseJsonArgs(args) === undefined ? undefined : 'json';
|
|
43
|
+
}
|
|
44
|
+
function filePathFromTool(tool, view) {
|
|
45
|
+
if (view !== undefined && 'path' in view && typeof view.path === 'string')
|
|
46
|
+
return view.path;
|
|
47
|
+
const parsed = parseJsonArgs(tool.argsFull ?? tool.argsText);
|
|
48
|
+
if (parsed !== null && typeof parsed === 'object') {
|
|
49
|
+
const record = parsed;
|
|
50
|
+
for (const key of ['file_path', 'path'])
|
|
51
|
+
if (typeof record[key] === 'string')
|
|
52
|
+
return record[key];
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
function languageFromPath(path) {
|
|
57
|
+
const language = path === undefined ? undefined : extname(path).slice(1).toLowerCase();
|
|
58
|
+
return language === '' ? undefined : language;
|
|
59
|
+
}
|
|
31
60
|
/** CC's collapsed text body keeps 3 lines (renderTruncatedContent). */
|
|
32
61
|
const TEXT_BODY_MAX_LINES = 3;
|
|
33
62
|
/** Diff bodies cap at the upstream chat row's 8 (dsh-client-ui-tool's
|
|
@@ -44,10 +73,11 @@ const dim = (text) => ({ text, tone: 'dim' });
|
|
|
44
73
|
const plain = (text) => ({ text, tone: 'plain' });
|
|
45
74
|
/** Tool-name color by category (mist-blue accents): read/search tools keep
|
|
46
75
|
* the brand blue, file-mutating tools get the warm gold accent, exec /
|
|
47
|
-
* terminal tools get mist cyan.
|
|
76
|
+
* terminal tools get mist cyan. Exported for the subagent card, which
|
|
77
|
+
* mirrors the transcript tool-card name styling. */
|
|
48
78
|
const TOOL_NAME_MUTATE = new Set(['edit', 'write', 'multiedit', 'notebookedit']);
|
|
49
79
|
const TOOL_NAME_EXEC = new Set(['bash', 'bashpersistent', 'sh', 'shell', 'terminal']);
|
|
50
|
-
function toolNameColor(raw) {
|
|
80
|
+
export function toolNameColor(raw) {
|
|
51
81
|
const n = raw.toLowerCase();
|
|
52
82
|
if (TOOL_NAME_MUTATE.has(n))
|
|
53
83
|
return 'toolNameMutate';
|
|
@@ -169,9 +199,9 @@ function clipHeaderArgs(args) {
|
|
|
169
199
|
return args;
|
|
170
200
|
return `${args.slice(0, HEADER_ARGS_BUDGET)}…`;
|
|
171
201
|
}
|
|
172
|
-
function HeaderTitle({ name, title, isTerminal, displayArgs, nameColor }) {
|
|
202
|
+
function HeaderTitle({ name, title, isTerminal, displayArgs, argsLanguage, nameColor }) {
|
|
173
203
|
if (title === undefined) {
|
|
174
|
-
return (_jsxs(_Fragment, { children: [_jsx(Box, { flexShrink: 0, children: _jsx(Text, { bold: true, color: nameColor, wrap: "truncate-end", children: name }) }), displayArgs !== '' && (
|
|
204
|
+
return (_jsxs(_Fragment, { children: [_jsx(Box, { flexShrink: 0, children: _jsx(Text, { bold: true, color: nameColor, wrap: "truncate-end", children: name }) }), displayArgs !== '' && (_jsxs(Box, { flexWrap: "nowrap", children: [_jsx(Text, { children: "(" }), _jsx(SyntaxText, { text: clipHeaderArgs(displayArgs), sourceText: displayArgs, language: argsLanguage }), _jsx(Text, { children: ")" })] }))] }));
|
|
175
205
|
}
|
|
176
206
|
if (isTerminal) {
|
|
177
207
|
return (_jsxs(_Fragment, { children: [_jsx(Box, { flexShrink: 0, children: _jsx(Text, { bold: true, color: nameColor, wrap: "truncate-end", children: name }) }), _jsx(Box, { flexWrap: "nowrap", children: _jsxs(Text, { children: ["(", title, ")"] }) })] }));
|
|
@@ -201,6 +231,10 @@ export function AssistantToolUseMessage({ tool, addMargin, verbose, isSelected =
|
|
|
201
231
|
// The settled view carries the applied diff / actual output; while running,
|
|
202
232
|
// the call view already shows the pending change (CC's pending Edit diff).
|
|
203
233
|
const view = tool.resultView ?? tool.callView;
|
|
234
|
+
const filePath = filePathFromTool(tool, view);
|
|
235
|
+
const syntaxLanguage = view?.card === 'read' || view?.card === 'generic' || view === undefined
|
|
236
|
+
? languageFromPath(filePath)
|
|
237
|
+
: undefined;
|
|
204
238
|
// presentResult may omit a title (terminal results carry output, not a
|
|
205
239
|
// command) — then the call view's title stands.
|
|
206
240
|
const headerTitle = tool.resultView?.title ?? tool.callView?.title;
|
|
@@ -238,6 +272,8 @@ export function AssistantToolUseMessage({ tool, addMargin, verbose, isSelected =
|
|
|
238
272
|
}
|
|
239
273
|
}
|
|
240
274
|
const cap = view?.card === 'diff' ? DIFF_BODY_MAX_LINES : TEXT_BODY_MAX_LINES;
|
|
275
|
+
const bodySource = body.map(line => line.text).join('\n');
|
|
276
|
+
const argsLanguage = jsonArgsLanguage(displayArgs);
|
|
241
277
|
// The footnote rides OUTSIDE the cap: it is a pointer, not content, and a
|
|
242
278
|
// long error body must not be the reason it disappears.
|
|
243
279
|
const lines = capLines(body, cap, verbose);
|
|
@@ -253,7 +289,7 @@ export function AssistantToolUseMessage({ tool, addMargin, verbose, isSelected =
|
|
|
253
289
|
return (_jsx(Box, { ref: viewportRef, flexDirection: "row", justifyContent: "space-between", marginTop: addMargin ? 1 : 0, width: "100%",
|
|
254
290
|
// Only selection paints a highlight; the configured treatment applies
|
|
255
291
|
// to an ordinary card. Diff line tints stay - they are content, not chrome.
|
|
256
|
-
backgroundColor: isSelected ? 'messageActionsBackground' : ordinaryBackground, children: _jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsxs(Box, { flexDirection: "row", flexWrap: "nowrap", minWidth: minWidth, children: [_jsx(ToolUseLoader, { shouldAnimate: isRunning, isUnresolved: isRunning, isError: isError, toolName: tool.name }), _jsx(HeaderTitle, { name: name, title: headerTitle, isTerminal: headerIsTerminal, displayArgs: displayArgs, nameColor: toolNameColor(tool.name) }), !isRunning && (_jsx(Box, { flexWrap: "nowrap", children: _jsx(Text, { dimColor: true, children: elapsedText }) }))] }), useSplitDiff && view?.card === 'diff' ? (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 3, flexShrink: 0, children: _jsx(Text, { dimColor: true, children: GUTTER_FIRST }) }), _jsx(SplitDiffView, { diffs: view.diffs, width: columns - 4, maxRows: DIFF_BODY_MAX_LINES, verbose: verbose, toolBackground: ordinaryToolBackground })] })) : (rendered.map((line, index) => (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 3, flexShrink: 0, children: _jsx(Text, { color: line.tone === 'add'
|
|
292
|
+
backgroundColor: isSelected ? 'messageActionsBackground' : ordinaryBackground, children: _jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsxs(Box, { flexDirection: "row", flexWrap: "nowrap", minWidth: minWidth, children: [_jsx(ToolUseLoader, { shouldAnimate: isRunning, isUnresolved: isRunning, isError: isError, toolName: tool.name }), _jsx(HeaderTitle, { name: name, title: headerTitle, isTerminal: headerIsTerminal, displayArgs: displayArgs, argsLanguage: argsLanguage, nameColor: toolNameColor(tool.name) }), !isRunning && (_jsx(Box, { flexWrap: "nowrap", children: _jsx(Text, { dimColor: true, children: elapsedText }) }))] }), useSplitDiff && view?.card === 'diff' ? (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 3, flexShrink: 0, children: _jsx(Text, { dimColor: true, children: GUTTER_FIRST }) }), _jsx(SplitDiffView, { diffs: view.diffs, width: columns - 4, maxRows: DIFF_BODY_MAX_LINES, verbose: verbose, toolBackground: ordinaryToolBackground })] })) : (rendered.map((line, index) => (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 3, flexShrink: 0, children: _jsx(Text, { color: line.tone === 'add'
|
|
257
293
|
? 'diffAddedWord'
|
|
258
294
|
: line.tone === 'del'
|
|
259
295
|
? 'diffRemovedWord'
|
|
@@ -269,5 +305,5 @@ export function AssistantToolUseMessage({ tool, addMargin, verbose, isSelected =
|
|
|
269
305
|
? 'subtle'
|
|
270
306
|
: line.tone === 'path'
|
|
271
307
|
? 'ide'
|
|
272
|
-
: undefined, dimColor: line.tone === 'dim', wrap: "wrap", children: line.text === '' ? ' ' : line.text }) })] }, index)))), useSplitDiff && footnote !== undefined && (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 3, flexShrink: 0, children: _jsx(Text, { dimColor: true, children: GUTTER_REST }) }), _jsx(Text, { color: "subtle", children: footnote })] }))] }) }));
|
|
308
|
+
: undefined, dimColor: line.tone === 'dim', wrap: "wrap", children: line.tone === 'plain' && syntaxLanguage !== undefined ? (_jsx(SyntaxText, { text: line.text, sourceText: bodySource, lineIndex: index, language: syntaxLanguage })) : (line.text === '' ? ' ' : line.text) }) })] }, index)))), useSplitDiff && footnote !== undefined && (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 3, flexShrink: 0, children: _jsx(Text, { dimColor: true, children: GUTTER_REST }) }), _jsx(Text, { color: "subtle", children: footnote })] }))] }) }));
|
|
273
309
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserPromptMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/messages/UserPromptMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"UserPromptMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/messages/UserPromptMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAMzB,KAAK,KAAK,GAAG;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,yDAAyD;IACzD,SAAS,EAAE,OAAO,CAAA;IAClB,wCAAwC;IACxC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,OAAO,CAAC,IAAI,IAAI,CAAA;CACjB,CAAA;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,IAAI,EACJ,SAAS,EACT,UAAkB,EAClB,OAAO,GACR,EAAE,KAAK,GAAG,KAAK,CAAC,SAAS,CA6BzB"}
|
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from '../../ui.js';
|
|
2
|
+
import { Box, Text, useTerminalSize } from '../../ui.js';
|
|
3
3
|
import { POINTER } from '../../cc/figures.js';
|
|
4
|
+
import { stringWidth } from '../../ink/stringWidth.js';
|
|
5
|
+
import { wrapWidth } from '../../sessions/format.js';
|
|
4
6
|
/**
|
|
5
7
|
* User prompt bubble: `❯ text` in bold briefLabelYou gold with no background
|
|
6
8
|
* fill (Kimi Code style: the user turn gets a distinct bold tint so it reads
|
|
7
9
|
* apart from assistant text; only selection mode paints a highlight).
|
|
8
10
|
*/
|
|
9
11
|
export function UserPromptMessage({ text, addMargin, isSelected = false, onClick, }) {
|
|
10
|
-
|
|
12
|
+
const { columns } = useTerminalSize();
|
|
13
|
+
const promptPrefix = `${POINTER} `;
|
|
14
|
+
const prefixWidth = stringWidth(promptPrefix);
|
|
15
|
+
// Wrap here instead of letting Ink wrap the whole Text node. Ink starts an
|
|
16
|
+
// automatic continuation at column zero, while a prompt needs a hanging
|
|
17
|
+
// indent for both explicit newlines and width-based visual lines.
|
|
18
|
+
// Leave a small safety margin for the ScrollBox edge/scrollbar. The Text
|
|
19
|
+
// nodes below are explicitly wrapped, so they must never be wrapped again by
|
|
20
|
+
// Ink; a second wrap would move the continuation back to column zero.
|
|
21
|
+
const lines = wrapWidth(text, Math.max(1, columns - prefixWidth - 3));
|
|
22
|
+
const continuationIndent = ' '.repeat(prefixWidth);
|
|
23
|
+
return (_jsx(Box, { flexDirection: "column", marginTop: addMargin ? 1 : 0, backgroundColor: isSelected ? 'messageActionsBackground' : undefined, paddingRight: 1, onClick: onClick, children: lines.map((line, index) => (_jsxs(Text, { color: "briefLabelYou", bold: true, wrap: "truncate-end", children: [index === 0 ? `${POINTER} ` : continuationIndent, line] }, index))) }));
|
|
11
24
|
}
|