@deepseek-harness-tui/dsh-tui 0.8.6 → 0.8.8

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 (120) hide show
  1. package/README.md +15 -5
  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/FileSuggestions.d.ts +8 -4
  19. package/lib/types/components/FileSuggestions.d.ts.map +1 -1
  20. package/lib/types/components/FileSuggestions.js +22 -12
  21. package/lib/types/components/HelpMenu.d.ts +8 -1
  22. package/lib/types/components/HelpMenu.d.ts.map +1 -1
  23. package/lib/types/components/HelpMenu.js +15 -4
  24. package/lib/types/components/LogoV2.d.ts +7 -1
  25. package/lib/types/components/LogoV2.d.ts.map +1 -1
  26. package/lib/types/components/LogoV2.js +13 -4
  27. package/lib/types/components/MessageList.d.ts +6 -2
  28. package/lib/types/components/MessageList.d.ts.map +1 -1
  29. package/lib/types/components/MessageList.js +17 -6
  30. package/lib/types/components/ModelPicker.js +2 -2
  31. package/lib/types/components/PromptInput.d.ts +1 -1
  32. package/lib/types/components/PromptInput.d.ts.map +1 -1
  33. package/lib/types/components/PromptInput.js +100 -36
  34. package/lib/types/components/SkillsPicker.js +2 -2
  35. package/lib/types/components/SplitDiffView.d.ts +1 -13
  36. package/lib/types/components/SplitDiffView.d.ts.map +1 -1
  37. package/lib/types/components/SplitDiffView.js +6 -105
  38. package/lib/types/components/SubagentCard.d.ts +9 -0
  39. package/lib/types/components/SubagentCard.d.ts.map +1 -0
  40. package/lib/types/components/SubagentCard.js +26 -0
  41. package/lib/types/components/SubagentDashboard.d.ts +13 -0
  42. package/lib/types/components/SubagentDashboard.d.ts.map +1 -0
  43. package/lib/types/components/SubagentDashboard.js +50 -0
  44. package/lib/types/components/SubagentDetailScene.d.ts +16 -0
  45. package/lib/types/components/SubagentDetailScene.d.ts.map +1 -0
  46. package/lib/types/components/SubagentDetailScene.js +141 -0
  47. package/lib/types/components/SyntaxText.d.ts +15 -0
  48. package/lib/types/components/SyntaxText.d.ts.map +1 -0
  49. package/lib/types/components/SyntaxText.js +34 -0
  50. package/lib/types/components/ThinkingToggle.d.ts +3 -10
  51. package/lib/types/components/ThinkingToggle.d.ts.map +1 -1
  52. package/lib/types/components/ThinkingToggle.js +4 -9
  53. package/lib/types/components/design-system/ListItem.d.ts.map +1 -1
  54. package/lib/types/components/design-system/ListItem.js +1 -1
  55. package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
  56. package/lib/types/components/messages/AssistantThinkingMessage.js +18 -7
  57. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +2 -0
  58. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  59. package/lib/types/components/messages/AssistantToolUseMessage.js +42 -6
  60. package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
  61. package/lib/types/components/messages/UserPromptMessage.js +15 -2
  62. package/lib/types/dsh-adapter/channel.d.ts +64 -3
  63. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  64. package/lib/types/dsh-adapter/channel.js +387 -37
  65. package/lib/types/dsh-adapter/contract.d.ts +67 -4
  66. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  67. package/lib/types/dsh-adapter/contract.js +149 -15
  68. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  69. package/lib/types/dsh-adapter/index.js +6 -6
  70. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  71. package/lib/types/dsh-adapter/plugin.js +38 -0
  72. package/lib/types/dsh-adapter/shortcuts.js +3 -3
  73. package/lib/types/dsh-adapter/subagents.d.ts +87 -0
  74. package/lib/types/dsh-adapter/subagents.d.ts.map +1 -0
  75. package/lib/types/dsh-adapter/subagents.js +204 -0
  76. package/lib/types/herdr.d.ts +28 -0
  77. package/lib/types/herdr.d.ts.map +1 -0
  78. package/lib/types/herdr.js +133 -0
  79. package/lib/types/i18n.d.ts +233 -32
  80. package/lib/types/i18n.d.ts.map +1 -1
  81. package/lib/types/i18n.js +87 -16
  82. package/lib/types/ink/ink.d.ts +4 -0
  83. package/lib/types/ink/ink.d.ts.map +1 -1
  84. package/lib/types/ink/ink.js +84 -38
  85. package/lib/types/ink/render-node-to-output.d.ts +6 -3
  86. package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
  87. package/lib/types/ink/render-node-to-output.js +33 -7
  88. package/lib/types/ink/selection.d.ts +29 -4
  89. package/lib/types/ink/selection.d.ts.map +1 -1
  90. package/lib/types/ink/selection.js +41 -5
  91. package/lib/types/minimalMode.d.ts +13 -0
  92. package/lib/types/minimalMode.d.ts.map +1 -0
  93. package/lib/types/minimalMode.js +17 -0
  94. package/lib/types/screens/Chat.d.ts +1 -1
  95. package/lib/types/screens/Chat.d.ts.map +1 -1
  96. package/lib/types/screens/Chat.js +94 -43
  97. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  98. package/lib/types/screens/StatusLine.js +6 -2
  99. package/lib/types/screens/TrajectoryScene.d.ts.map +1 -1
  100. package/lib/types/screens/TrajectoryScene.js +4 -2
  101. package/lib/types/theme.d.ts +8 -0
  102. package/lib/types/theme.d.ts.map +1 -1
  103. package/lib/types/theme.js +38 -14
  104. package/lib/types/tips.d.ts.map +1 -1
  105. package/lib/types/tips.js +25 -7
  106. package/lib/types/update.d.ts +18 -0
  107. package/lib/types/update.d.ts.map +1 -1
  108. package/lib/types/update.js +73 -2
  109. package/lib/types/utils/execFileNoThrow.d.ts +3 -20
  110. package/lib/types/utils/execFileNoThrow.d.ts.map +1 -1
  111. package/lib/types/utils/execFileNoThrow.js +53 -32
  112. package/lib/types/utils/externalEditor.d.ts +12 -11
  113. package/lib/types/utils/externalEditor.d.ts.map +1 -1
  114. package/lib/types/utils/externalEditor.js +11 -11
  115. package/lib/types/utils/fileSuggestions.d.ts +19 -0
  116. package/lib/types/utils/fileSuggestions.d.ts.map +1 -0
  117. package/lib/types/utils/fileSuggestions.js +40 -0
  118. package/lib/types/utils/mentions.d.ts.map +1 -1
  119. package/lib/types/utils/mentions.js +3 -1
  120. package/package.json +81 -74
@@ -4,98 +4,15 @@ import { Box, Text } from '../ui.js';
4
4
  import * as JsDiff from 'diff';
5
5
  import { extname } from 'node:path';
6
6
  import { getCliHighlightPromise } from '../cc/cliHighlight.js';
7
- import { SYNTAX_CLASS_TO_TOKEN, chalkFromToken } from '../cc/syntaxTheme.js';
7
+ import { highlightLines, syntaxThemeSignature } from '../cc/syntaxRuns.js';
8
8
  // Backward-compatible re-export: repro scripts import chalkFromToken from
9
9
  // this module's old home.
10
10
  export { chalkFromToken } from '../cc/syntaxTheme.js';
11
+ export { parseAnsiRuns, highlightLines } from '../cc/syntaxRuns.js';
11
12
  import { getTheme } from '../theme.js';
12
13
  import { useTheme } from './design-system/ThemeProvider.js';
13
14
  /** Replace tabs so width math and alignment hold (pi convention). */
14
15
  const expandTabs = (text) => text.replaceAll('\t', ' ');
15
- // --- syntax highlighting ----------------------------------------------------
16
- // The hljs-class → theme-token map and chalkFromToken live in
17
- // ../cc/syntaxTheme.ts (shared with the markdown code-block renderer).
18
- /** ANSI 16-color SGR codes → rgb() strings (the dark-ansi palette path). */
19
- const ANSI16 = {
20
- 30: 'rgb(0,0,0)', 31: 'rgb(128,0,0)', 32: 'rgb(0,128,0)', 33: 'rgb(128,128,0)',
21
- 34: 'rgb(0,0,128)', 35: 'rgb(128,0,128)', 36: 'rgb(0,128,128)', 37: 'rgb(192,192,192)',
22
- 90: 'rgb(128,128,128)', 91: 'rgb(255,0,0)', 92: 'rgb(0,255,0)', 93: 'rgb(255,255,0)',
23
- 94: 'rgb(0,0,255)', 95: 'rgb(255,0,255)', 96: 'rgb(0,255,255)', 97: 'rgb(255,255,255)',
24
- };
25
- /** Parse a cli-highlight ANSI string into colored runs, preserving open
26
- * spans across embedded newlines (multi-line strings/comments). Handles
27
- * truecolor 38;2, 256-color 38;5 (tmux / FORCE_COLOR=2, issue #250 P1-1),
28
- * and single-code 16-color SGR. */
29
- export function parseAnsiRuns(text) {
30
- const runs = [];
31
- let color;
32
- let rest = text;
33
- // eslint-disable-next-line no-control-regex -- parsing SGR is the point
34
- const sgr = /\x1b\[([0-9;]+)m/;
35
- while (rest.length > 0) {
36
- const match = sgr.exec(rest);
37
- const chunk = match === null ? rest : rest.slice(0, match.index);
38
- if (chunk !== '')
39
- runs.push(color === undefined ? { text: chunk } : { text: chunk, color });
40
- if (match === null)
41
- break;
42
- const codes = match[1].split(';').map(Number);
43
- if (codes.includes(0) || codes.includes(39))
44
- color = undefined;
45
- else if (codes[0] === 38 && codes[1] === 2 && codes.length >= 5) {
46
- color = `rgb(${codes[2]},${codes[3]},${codes[4]})`;
47
- }
48
- else if (codes[0] === 38 && codes[1] === 5 && codes.length >= 3) {
49
- color = `ansi256(${codes[2]})`;
50
- }
51
- else if (codes.length === 1 && ANSI16[codes[0]] !== undefined) {
52
- color = ANSI16[codes[0]];
53
- }
54
- rest = rest.slice(match.index + match[0].length);
55
- }
56
- return runs;
57
- }
58
- /** Split flat runs into per-line run arrays at embedded newlines. */
59
- function splitRunsToLines(runs) {
60
- const lines = [[]];
61
- for (const run of runs) {
62
- const parts = run.text.split('\n');
63
- for (let i = 0; i < parts.length; i++) {
64
- if (i > 0)
65
- lines.push([]);
66
- if (parts[i] !== '') {
67
- lines[lines.length - 1].push(run.color === undefined ? { text: parts[i] } : { text: parts[i], color: run.color });
68
- }
69
- }
70
- }
71
- return lines;
72
- }
73
- /** Whole-hunk syntax runs per line, cached per (theme signature + language
74
- * + text): one entry survives any amount of card re-rendering, and the
75
- * palette change invalidates it (issue #250, P1-2 + P2-8). */
76
- const syntaxCache = new Map();
77
- const SYNTAX_CACHE_MAX = 40;
78
- export function highlightLines(text, language, hl, chTheme, themeSig) {
79
- if (hl === null || chTheme === undefined || language === undefined)
80
- return undefined;
81
- if (text === '' || !hl.supportsLanguage(language))
82
- return undefined;
83
- const key = `${themeSig}${language}${text}`;
84
- const cached = syntaxCache.get(key);
85
- if (cached !== undefined)
86
- return cached;
87
- let lines;
88
- try {
89
- lines = splitRunsToLines(parseAnsiRuns(hl.highlight(text, { language, theme: chTheme })));
90
- }
91
- catch {
92
- return undefined;
93
- }
94
- if (syntaxCache.size >= SYNTAX_CACHE_MAX)
95
- syntaxCache.clear();
96
- syntaxCache.set(key, lines);
97
- return lines;
98
- }
99
16
  /**
100
17
  * Merge syntax runs with word-diff change flags over the same string: both
101
18
  * partition it, so an offset sweep yields runs carrying a syntax color AND
@@ -306,24 +223,8 @@ export function SplitDiffView({ diffs, width, maxRows, verbose, toolBackground =
306
223
  const [themeName] = useTheme();
307
224
  // Resolved-palette signature: covers dark→light, light→dark, AND the
308
225
  // `auto` base flip where the name never changes (issue #250, P1-2).
309
- const themeSig = React.useMemo(() => {
310
- const theme = getTheme(themeName);
311
- return Object.values(SYNTAX_CLASS_TO_TOKEN)
312
- .map(key => theme[key] ?? '')
313
- .join('|');
314
- }, [themeName]);
315
- const chTheme = React.useMemo(() => {
316
- const theme = getTheme(themeName);
317
- const out = {};
318
- for (const [tokenClass, tokenKey] of Object.entries(SYNTAX_CLASS_TO_TOKEN)) {
319
- const value = theme[tokenKey];
320
- if (value !== undefined)
321
- out[tokenClass] = chalkFromToken(value);
322
- }
323
- return out;
324
- // themeSig changes exactly when any syntax token's resolved value does.
325
- // eslint-disable-next-line react-hooks/exhaustive-deps
326
- }, [themeSig]);
226
+ const syntaxTheme = React.useMemo(() => getTheme(themeName), [themeName]);
227
+ const themeSig = React.useMemo(() => syntaxThemeSignature(syntaxTheme), [syntaxTheme]);
327
228
  // Alignment is text-only and capped BEFORE styling: the highlighter and
328
229
  // word-diff work lands only on the visible slice (issue #250, P2-8).
329
230
  const rows = [];
@@ -348,8 +249,8 @@ export function SplitDiffView({ diffs, width, maxRows, verbose, toolBackground =
348
249
  const fileSyntax = diffs.map(diff => {
349
250
  const language = extname(diff.path).replace(/^\./, '') || undefined;
350
251
  return {
351
- old: highlightLines(expandTabs(diff.oldText ?? ''), language, hl, chTheme, themeSig),
352
- next: highlightLines(expandTabs(diff.newText), language, hl, chTheme, themeSig),
252
+ old: highlightLines(expandTabs(diff.oldText ?? ''), language, hl, syntaxTheme, themeSig),
253
+ next: highlightLines(expandTabs(diff.newText), language, hl, syntaxTheme, themeSig),
353
254
  };
354
255
  });
355
256
  return (_jsxs(Box, { flexDirection: "column", width: paneWidth * 2 + 1, children: [visible.map((row, index) => {
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import type { SubagentState } from '../dsh-adapter/subagents.js';
3
+ export interface SubagentCardProps {
4
+ subagent: SubagentState;
5
+ focused?: boolean;
6
+ onClick?: () => void;
7
+ }
8
+ export declare function SubagentCard({ subagent, focused, onClick }: SubagentCardProps): React.ReactNode;
9
+ //# sourceMappingURL=SubagentCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SubagentCard.d.ts","sourceRoot":"","sources":["../../../src/components/SubagentCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAShE,MAAM,WAAW,iBAAiB;IAAG,QAAQ,EAAE,aAAa,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE;AAEvG,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,iBAAiB,GAAG,KAAK,CAAC,SAAS,CA4B/F"}
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from '../ui.js';
3
+ import { t } from '../i18n.js';
4
+ import { isMinimalMode } from '../minimalMode.js';
5
+ function formatDuration(ms) {
6
+ const seconds = Math.floor(ms / 1000);
7
+ return seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}m${seconds % 60}s`;
8
+ }
9
+ export function SubagentCard({ subagent, focused, onClick }) {
10
+ const running = subagent.status === 'running' || subagent.status === 'starting';
11
+ const elapsed = subagent.completedAt ? subagent.completedAt - subagent.startedAt : Date.now() - subagent.startedAt;
12
+ const total = subagent.tokens?.total ?? ((subagent.tokens?.input ?? 0) + (subagent.tokens?.output ?? 0) || 0);
13
+ // Live preview: the newest streamed line rides under the header while the
14
+ // subagent runs, then folds away — the dashboard stays one line per settled
15
+ // subagent.
16
+ const liveLine = running ? subagent.output[subagent.output.length - 1] : undefined;
17
+ const minimal = isMinimalMode();
18
+ const glyph = running ? (minimal ? '·' : '🟡')
19
+ : subagent.status === 'failed' || subagent.status === 'cancelled' ? (minimal ? '×' : '🔴')
20
+ : (minimal ? '✓' : '🟢');
21
+ const glyphColor = minimal ? undefined
22
+ : running ? 'warning'
23
+ : subagent.status === 'failed' || subagent.status === 'cancelled' ? 'error'
24
+ : 'success';
25
+ return _jsxs(Box, { flexDirection: "column", paddingLeft: 1, marginBottom: 1, onClick: onClick, children: [_jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Text, { color: glyphColor, children: glyph }), _jsx(Text, { bold: true, color: focused ? 'claude' : undefined, children: `${t('subagent-card-prefix')}${subagent.description}` }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: ' · ' }), _jsx(Text, { children: subagent.model ?? subagent.provider ?? 'default' }), _jsx(Text, { dimColor: true, children: ` · ${formatDuration(elapsed)} · ${total || '—'} tok · ${subagent.toolCalls.length} tools` })] })] }), liveLine && _jsx(Text, { dimColor: true, wrap: "truncate", children: ` │ ${liveLine}` })] });
26
+ }
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import type { SubagentState } from '../dsh-adapter/subagents.js';
3
+ export interface SubagentDashboardProps {
4
+ subagents: SubagentState[];
5
+ onClose: () => void;
6
+ onSelect?: (agentId: string) => void;
7
+ }
8
+ /**
9
+ * SubagentDashboard — overlay panel showing all active/recent subagents.
10
+ * Keyboard: up/down to navigate, Enter to view detail, Esc to close.
11
+ */
12
+ export declare function SubagentDashboard({ subagents, onClose, onSelect }: SubagentDashboardProps): React.ReactNode;
13
+ //# sourceMappingURL=SubagentDashboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SubagentDashboard.d.ts","sourceRoot":"","sources":["../../../src/components/SubagentDashboard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAMhE,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,aAAa,EAAE,CAAA;IAC1B,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CACrC;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,OAAO,EACP,QAAQ,EACT,EAAE,sBAAsB,GAAG,KAAK,CAAC,SAAS,CAoG1C"}
@@ -0,0 +1,50 @@
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 { SubagentCard } from './SubagentCard.js';
5
+ import { t } from '../i18n.js';
6
+ import { Divider } from './design-system/Divider.js';
7
+ import { isPlainReturnInput } from '../utils/modifiers.js';
8
+ /**
9
+ * SubagentDashboard — overlay panel showing all active/recent subagents.
10
+ * Keyboard: up/down to navigate, Enter to view detail, Esc to close.
11
+ */
12
+ export function SubagentDashboard({ subagents, onClose, onSelect }) {
13
+ const [focusIndex, setFocusIndex] = React.useState(0);
14
+ const scrollRef = React.useRef(null);
15
+ const { rows, columns } = useTerminalSize();
16
+ useInput((input, key, event) => {
17
+ if (key.escape || (key.ctrl && input === 'c')) {
18
+ event.stopImmediatePropagation();
19
+ onClose();
20
+ return;
21
+ }
22
+ if (key.upArrow) {
23
+ event.stopImmediatePropagation();
24
+ setFocusIndex(i => Math.max(0, i - 1));
25
+ scrollRef.current?.scrollBy(-3);
26
+ return;
27
+ }
28
+ if (key.downArrow) {
29
+ event.stopImmediatePropagation();
30
+ setFocusIndex(i => Math.min(subagents.length - 1, i + 1));
31
+ scrollRef.current?.scrollBy(3);
32
+ return;
33
+ }
34
+ if (isPlainReturnInput(input, key) && onSelect) {
35
+ event.stopImmediatePropagation();
36
+ const selected = subagents[focusIndex];
37
+ if (selected)
38
+ onSelect(selected.agentId);
39
+ return;
40
+ }
41
+ // Consume all input while dashboard is open
42
+ event.stopImmediatePropagation();
43
+ });
44
+ const running = subagents.filter(s => s.status === 'running').length;
45
+ const completed = subagents.filter(s => s.status === 'completed').length;
46
+ const failed = subagents.filter(s => s.status === 'failed').length;
47
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [_jsx(Divider, { color: "claude", title: t('subagent-dashboard-title'), padding: 4 }), _jsxs(Box, { flexDirection: "row", gap: 3, marginTop: 1, marginBottom: 1, children: [_jsxs(Text, { children: [_jsx(Text, { color: "claude", children: running }), _jsxs(Text, { dimColor: true, children: [" ", t('subagent-count-running')] })] }), _jsxs(Text, { children: [_jsx(Text, { color: "success", children: completed }), _jsxs(Text, { dimColor: true, children: [" ", t('subagent-count-completed')] })] }), failed > 0 && (_jsxs(Text, { children: [_jsx(Text, { color: "error", children: failed }), _jsxs(Text, { dimColor: true, children: [" ", t('subagent-count-failed')] })] }))] }), _jsx(Box, { flexDirection: "column", maxHeight: Math.max(10, rows - 10), marginTop: 1, children: _jsx(ScrollBox, { ref: scrollRef, flexDirection: "column", flexGrow: 1, children: subagents.length === 0 ? (_jsxs(Box, { flexDirection: "column", alignItems: "center", marginTop: Math.max(2, Math.floor((rows - 16) / 3)), children: [_jsx(Text, { dimColor: true, children: '○' }), _jsx(Text, { dimColor: true, children: t('subagent-none') }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: t('subagent-empty-hint') }) })] })) : (subagents.map((subagent, index) => (_jsxs(Box, { flexDirection: "column", children: [_jsx(SubagentCard, { subagent: subagent, focused: index === focusIndex }), index < subagents.length - 1 && (_jsx(Text, { dimColor: true, children: '─'.repeat(Math.max(20, Math.min(72, columns - 6))) }))] }, subagent.agentId)))) }) }), _jsx(Divider, { color: "subtle", title: "", padding: 4 }), _jsx(Box, { marginTop: 0, children: _jsx(Text, { dimColor: true, children: onSelect
48
+ ? t('subagent-dashboard-hint-detail')
49
+ : t('subagent-dashboard-hint-basic') }) })] }));
50
+ }
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import type { SubagentState } from '../dsh-adapter/subagents.js';
3
+ export interface SubagentDetailSceneProps {
4
+ subagent: SubagentState;
5
+ onBack: () => void;
6
+ onInterrupt?: (agentId: string) => void;
7
+ }
8
+ /**
9
+ * SubagentDetailScene — full-screen paged detail view for one subagent.
10
+ * Header block (identity + stats) stays fixed; the body pages through
11
+ * 摘要 / 输出 / 工具 with ←/→. Follow-up delivery was removed: the official
12
+ * seam only accepts continuable children, and one-shot spawn children are
13
+ * disposed at settlement, so the affordance would be a dead control.
14
+ */
15
+ export declare function SubagentDetailScene({ subagent, onBack, onInterrupt, }: SubagentDetailSceneProps): React.ReactNode;
16
+ //# sourceMappingURL=SubagentDetailScene.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SubagentDetailScene.d.ts","sourceRoot":"","sources":["../../../src/components/SubagentDetailScene.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAsGhE,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,aAAa,CAAA;IACvB,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CACxC;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,QAAQ,EACR,MAAM,EACN,WAAW,GACZ,EAAE,wBAAwB,GAAG,KAAK,CAAC,SAAS,CA+L5C"}
@@ -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
  }