@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.
Files changed (105) hide show
  1. package/README.md +13 -3
  2. package/bin/dsh-tui.js +269 -218
  3. package/cordis.patch.yml +48 -22
  4. package/lib/types/cc/figures.d.ts.map +1 -1
  5. package/lib/types/cc/figures.js +14 -13
  6. package/lib/types/cc/syntaxRuns.d.ts +16 -0
  7. package/lib/types/cc/syntaxRuns.d.ts.map +1 -0
  8. package/lib/types/cc/syntaxRuns.js +79 -0
  9. package/lib/types/commands.d.ts +14 -0
  10. package/lib/types/commands.d.ts.map +1 -1
  11. package/lib/types/commands.js +20 -1
  12. package/lib/types/components/Chat/SubagentMessage.d.ts +19 -0
  13. package/lib/types/components/Chat/SubagentMessage.d.ts.map +1 -0
  14. package/lib/types/components/Chat/SubagentMessage.js +75 -0
  15. package/lib/types/components/EffortInputBorder.d.ts.map +1 -1
  16. package/lib/types/components/EffortInputBorder.js +2 -2
  17. package/lib/types/components/EffortTierBadge.js +1 -1
  18. package/lib/types/components/HelpMenu.d.ts +8 -1
  19. package/lib/types/components/HelpMenu.d.ts.map +1 -1
  20. package/lib/types/components/HelpMenu.js +15 -4
  21. package/lib/types/components/LogoV2.d.ts +3 -1
  22. package/lib/types/components/LogoV2.d.ts.map +1 -1
  23. package/lib/types/components/LogoV2.js +2 -2
  24. package/lib/types/components/MessageList.d.ts +6 -2
  25. package/lib/types/components/MessageList.d.ts.map +1 -1
  26. package/lib/types/components/MessageList.js +17 -6
  27. package/lib/types/components/ModelPicker.js +2 -2
  28. package/lib/types/components/PromptInput.d.ts +1 -1
  29. package/lib/types/components/PromptInput.d.ts.map +1 -1
  30. package/lib/types/components/PromptInput.js +75 -15
  31. package/lib/types/components/SkillsPicker.js +2 -2
  32. package/lib/types/components/SplitDiffView.d.ts +1 -13
  33. package/lib/types/components/SplitDiffView.d.ts.map +1 -1
  34. package/lib/types/components/SplitDiffView.js +6 -105
  35. package/lib/types/components/SubagentCard.d.ts +9 -0
  36. package/lib/types/components/SubagentCard.d.ts.map +1 -0
  37. package/lib/types/components/SubagentCard.js +26 -0
  38. package/lib/types/components/SubagentDashboard.d.ts +13 -0
  39. package/lib/types/components/SubagentDashboard.d.ts.map +1 -0
  40. package/lib/types/components/SubagentDashboard.js +50 -0
  41. package/lib/types/components/SubagentDetailScene.d.ts +16 -0
  42. package/lib/types/components/SubagentDetailScene.d.ts.map +1 -0
  43. package/lib/types/components/SubagentDetailScene.js +141 -0
  44. package/lib/types/components/SyntaxText.d.ts +15 -0
  45. package/lib/types/components/SyntaxText.d.ts.map +1 -0
  46. package/lib/types/components/SyntaxText.js +34 -0
  47. package/lib/types/components/ThinkingToggle.d.ts +3 -10
  48. package/lib/types/components/ThinkingToggle.d.ts.map +1 -1
  49. package/lib/types/components/ThinkingToggle.js +4 -9
  50. package/lib/types/components/design-system/ListItem.d.ts.map +1 -1
  51. package/lib/types/components/design-system/ListItem.js +1 -1
  52. package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
  53. package/lib/types/components/messages/AssistantThinkingMessage.js +18 -7
  54. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +2 -0
  55. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  56. package/lib/types/components/messages/AssistantToolUseMessage.js +42 -6
  57. package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
  58. package/lib/types/components/messages/UserPromptMessage.js +15 -2
  59. package/lib/types/dsh-adapter/channel.d.ts +53 -2
  60. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  61. package/lib/types/dsh-adapter/channel.js +294 -7
  62. package/lib/types/dsh-adapter/contract.d.ts +27 -4
  63. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  64. package/lib/types/dsh-adapter/contract.js +56 -10
  65. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  66. package/lib/types/dsh-adapter/index.js +9 -3
  67. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  68. package/lib/types/dsh-adapter/plugin.js +38 -0
  69. package/lib/types/dsh-adapter/shortcuts.js +3 -3
  70. package/lib/types/dsh-adapter/subagents.d.ts +87 -0
  71. package/lib/types/dsh-adapter/subagents.d.ts.map +1 -0
  72. package/lib/types/dsh-adapter/subagents.js +204 -0
  73. package/lib/types/herdr.d.ts +28 -0
  74. package/lib/types/herdr.d.ts.map +1 -0
  75. package/lib/types/herdr.js +133 -0
  76. package/lib/types/i18n.d.ts +215 -31
  77. package/lib/types/i18n.d.ts.map +1 -1
  78. package/lib/types/i18n.js +65 -14
  79. package/lib/types/ink/ink.d.ts +4 -0
  80. package/lib/types/ink/ink.d.ts.map +1 -1
  81. package/lib/types/ink/ink.js +46 -15
  82. package/lib/types/minimalMode.d.ts +13 -0
  83. package/lib/types/minimalMode.d.ts.map +1 -0
  84. package/lib/types/minimalMode.js +17 -0
  85. package/lib/types/screens/Chat.d.ts +1 -1
  86. package/lib/types/screens/Chat.d.ts.map +1 -1
  87. package/lib/types/screens/Chat.js +94 -43
  88. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  89. package/lib/types/screens/StatusLine.js +6 -2
  90. package/lib/types/screens/TrajectoryScene.d.ts.map +1 -1
  91. package/lib/types/screens/TrajectoryScene.js +4 -2
  92. package/lib/types/theme.d.ts +8 -0
  93. package/lib/types/theme.d.ts.map +1 -1
  94. package/lib/types/theme.js +38 -14
  95. package/lib/types/tips.js +3 -3
  96. package/lib/types/update.d.ts +18 -0
  97. package/lib/types/update.d.ts.map +1 -1
  98. package/lib/types/update.js +73 -2
  99. package/lib/types/utils/execFileNoThrow.d.ts +3 -20
  100. package/lib/types/utils/execFileNoThrow.d.ts.map +1 -1
  101. package/lib/types/utils/execFileNoThrow.js +53 -32
  102. package/lib/types/utils/externalEditor.d.ts +12 -11
  103. package/lib/types/utils/externalEditor.d.ts.map +1 -1
  104. package/lib/types/utils/externalEditor.js +11 -11
  105. 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, mirroring Claude Code's ThinkingToggle.tsx: a
4
- * permission-colored Pane with a bold title, the Enabled/Disabled select
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, confirmationPending, }: {
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;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,EAC7B,YAAY,EACZ,UAAU,EACV,mBAAmB,GACpB,EAAE;IACD,YAAY,EAAE,OAAO,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,qEAAqE;IACrE,mBAAmB,EAAE,OAAO,GAAG,IAAI,CAAA;CACpC,GAAG,KAAK,CAAC,SAAS,CA+ClB"}
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, mirroring Claude Code's ThinkingToggle.tsx: a
9
- * permission-colored Pane with a bold title, the Enabled/Disabled select
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, confirmationPending, }) {
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') })] }), confirmationPending !== null ? (_jsxs(Box, { flexDirection: "column", marginBottom: 1, gap: 1, children: [_jsx(Text, { color: "warning", children: t('thinking-mid-warning') }), _jsx(Text, { color: "warning", children: t('thinking-proceed') })] })) : (_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: confirmationPending !== null ? t('hint-confirm-cancel') : t('hint-confirm-exit') }) })] }));
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,CAkEjC"}
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;AAmBzB,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,CAwGzB"}
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 { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
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: [_jsxs(Text, { dimColor: true, italic: true, children: [marker, " ", t('thinking-label'), duration, "\u2026"] }), _jsx(Box, { flexDirection: "column", paddingLeft: 2, height: PREVIEW_ROWS, flexShrink: 0, overflow: "hidden", children: rows.map((line, i) => (_jsx(Text, { dimColor: true, italic: true, wrap: i === rows.length - 1 ? 'truncate-start' : 'truncate', children: i === 0 && clipped ? `…${line}` : line }, i))) })] }));
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: _jsxs(Text, { dimColor: true, italic: true, children: [marker, " ", t('thinking-label'), duration, " ", t('hint-expand-ctrl-o')] }) }));
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: [_jsxs(Text, { dimColor: true, italic: true, children: [marker, " ", t('thinking-label'), duration, "\u2026"] }), _jsx(Box, { paddingLeft: 2, children: _jsx(StreamingMarkdown, { dimColor: true, children: thinking }) })] }));
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;AAIzB,OAAO,KAAK,EAA8C,OAAO,EAAE,MAAM,8BAA8B,CAAA;AAIvG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAG9D,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;AAkOD;;;;;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,CAyJzB"}
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 !== '' && (_jsx(Box, { flexWrap: "nowrap", children: _jsxs(Text, { children: ["(", clipHeaderArgs(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;AAIzB,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,CAczB"}
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
- return (_jsx(Box, { flexDirection: "column", marginTop: addMargin ? 1 : 0, backgroundColor: isSelected ? 'messageActionsBackground' : undefined, paddingRight: 1, onClick: onClick, children: _jsxs(Text, { color: "briefLabelYou", bold: true, children: [POINTER, " ", text] }) }));
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
  }