@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
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { type Tip } from '../tips.js';
3
+ import { type UpstreamDriftSummary } from '../dsh-adapter/contract.js';
3
4
  /**
4
5
  * The header splash: one layout, two phases. The **opening** (~3.4s, once)
5
6
  * plays the hand-drawn whale animation (blink → water-spout bloom → tail
@@ -14,7 +15,7 @@ import { type Tip } from '../tips.js';
14
15
  * the whale the welcome tagline, centered under the art, in ice
15
16
  * blue. Narrow terminals drop the whale and keep the text column.
16
17
  */
17
- export declare function LogoV2({ model, effort, cwd, skipIntro, tip, }: {
18
+ export declare function LogoV2({ model, effort, cwd, skipIntro, tip, whale, drift, }: {
18
19
  model: string;
19
20
  effort?: string | undefined;
20
21
  cwd: string;
@@ -22,5 +23,10 @@ export declare function LogoV2({ model, effort, cwd, skipIntro, tip, }: {
22
23
  skipIntro?: boolean;
23
24
  /** Test seam: pin the startup tip line (probes need a deterministic tip). */
24
25
  tip?: Tip;
26
+ /** Show the pixel whale art (settings `dsh-tui.whale`); off → text-only header. */
27
+ whale?: boolean;
28
+ /** Test seam: pin/suppress the upstream-drift notice (`null` forces it off;
29
+ * `undefined` — the production default — auto-detects the install). */
30
+ drift?: UpstreamDriftSummary | null;
25
31
  }): React.ReactNode;
26
32
  //# sourceMappingURL=LogoV2.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"LogoV2.d.ts","sourceRoot":"","sources":["../../../src/components/LogoV2.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAAiB,KAAK,GAAG,EAAE,MAAM,YAAY,CAAA;AAuDpD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,MAAM,CAAC,EACrB,KAAK,EACL,MAAM,EACN,GAAG,EACH,SAAiB,EACjB,GAAG,GACJ,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,iFAAiF;IACjF,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,6EAA6E;IAC7E,GAAG,CAAC,EAAE,GAAG,CAAA;CACV,GAAG,KAAK,CAAC,SAAS,CAqFlB"}
1
+ {"version":3,"file":"LogoV2.d.ts","sourceRoot":"","sources":["../../../src/components/LogoV2.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAAiB,KAAK,GAAG,EAAE,MAAM,YAAY,CAAA;AACpD,OAAO,EAAoD,KAAK,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAuDxH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,MAAM,CAAC,EACrB,KAAK,EACL,MAAM,EACN,GAAG,EACH,SAAiB,EACjB,GAAG,EACH,KAAY,EACZ,KAAK,GACN,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,iFAAiF;IACjF,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,6EAA6E;IAC7E,GAAG,CAAC,EAAE,GAAG,CAAA;IACT,mFAAmF;IACnF,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;2EACuE;IACvE,KAAK,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAA;CACpC,GAAG,KAAK,CAAC,SAAS,CAyGlB"}
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React from 'react';
3
- import { getLang, t as tr } from '../i18n.js';
3
+ import { getLang, t as tr, tOr } from '../i18n.js';
4
4
  import { pickRandomTip } from '../tips.js';
5
+ import { upstreamDriftSummary, UPSTREAM_VALIDATED_VERSION } from '../dsh-adapter/contract.js';
5
6
  import { readFileSync } from 'node:fs';
6
7
  import { dirname, join } from 'node:path';
7
8
  import { fileURLToPath } from 'node:url';
@@ -65,7 +66,7 @@ function capitalize(text) {
65
66
  * the whale the welcome tagline, centered under the art, in ice
66
67
  * blue. Narrow terminals drop the whale and keep the text column.
67
68
  */
68
- export function LogoV2({ model, effort, cwd, skipIntro = false, tip, }) {
69
+ export function LogoV2({ model, effort, cwd, skipIntro = false, tip, whale = true, drift, }) {
69
70
  const [step, setStep] = React.useState(skipIntro ? OPENING_SEQUENCE.length : 0);
70
71
  const settled = step >= OPENING_SEQUENCE.length;
71
72
  // Opening clock: drives the shimmer sweep and big-text highlight only
@@ -89,7 +90,7 @@ export function LogoV2({ model, effort, cwd, skipIntro = false, tip, }) {
89
90
  const wordmarkRGB = parseRGB(theme.claude) ?? BRAND;
90
91
  const wordmarkShimmerRGB = parseRGB(theme.claudeShimmer) ?? ICE;
91
92
  const taglineRGB = parseRGB(theme.claudeBlue_FOR_SYSTEM_SPINNER) ?? ICE;
92
- const showWhale = columns >= WHALE_MIN_COLUMNS;
93
+ const showWhale = whale && columns >= WHALE_MIN_COLUMNS;
93
94
  const frameIndex = settled ? STANDARD_FRAME_INDEX : OPENING_SEQUENCE[step].frame;
94
95
  // Frozen clock for the settled header: t=0 parks every sweep highlight
95
96
  // off-screen, leaving the static gradient behind.
@@ -99,11 +100,19 @@ export function LogoV2({ model, effort, cwd, skipIntro = false, tip, }) {
99
100
  // repaint (language switch, terminal resize), or the line would flicker.
100
101
  // `tip` is a test seam; production always passes undefined and rolls.
101
102
  const [randomTip] = React.useState(() => tip ?? pickRandomTip());
103
+ // Upstream-drift notice, merged to one line: computed once per mount from
104
+ // the same memoized contract data the adapter checks (undefined when the
105
+ // install matches). `drift` is a test seam to pin or suppress it.
106
+ const [driftLine] = React.useState(() => drift === undefined ? upstreamDriftSummary() : drift);
102
107
  // Indent that centers the tagline under the whale art's bounding box.
103
108
  const welcomePad = showWhale
104
109
  ? Math.max(0, Math.round(WHALE_CENTER - stringWidth(tagline) / 2))
105
110
  : 2;
106
111
  const bigDeepSeek = renderBigText('DEEPSEEK', t, wordmarkRGB, taglineRGB, FLASH, 60);
107
112
  const bigHarness = renderBigText('HARNESS', t, taglineRGB, PALE, FLASH, 60);
108
- return (_jsxs(Box, { ref: ref, flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { flexDirection: "row", gap: 2, width: "100%", alignItems: "center", children: [showWhale && _jsx(WhaleArt, { frameIndex: frameIndex, width: FULL_WHALE_WIDTH }), _jsxs(Box, { flexDirection: "column", flexShrink: 1, children: [_jsxs(Text, { wrap: "truncate-end", children: [sweep('✦ dsh-TUI', t, wordmarkRGB, wordmarkShimmerRGB, 60), _jsx(Text, { dimColor: true, children: ' v' + VERSION })] }), bigDeepSeek.map((row, index) => (_jsx(Text, { wrap: "truncate-end", children: row }, `ds-${index}`))), bigHarness.map((row, index) => (_jsx(Text, { wrap: "truncate-end", children: row }, `h-${index}`))), _jsxs(Text, { wrap: "truncate-end", children: [model, effort !== undefined && _jsx(Text, { dimColor: true, children: ' · ' + capitalize(effort) + ' effort' })] }), _jsx(Text, { dimColor: true, wrap: "truncate-end", children: cwd }), _jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { dimColor: true, children: tr('logo-tip-prefix') }), getLang() === 'zh' ? randomTip.zh : randomTip.en, _jsx(Text, { dimColor: true, children: ' · /tips ' + tr('logo-tip-more') })] })] })] }), _jsx(Box, { marginTop: 1, paddingLeft: welcomePad, children: _jsx(Text, { children: sweep(tagline, t, taglineRGB, FLASH, 60) }) })] }));
113
+ return (_jsxs(Box, { ref: ref, flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { flexDirection: "row", gap: 2, width: "100%", alignItems: "center", children: [showWhale && _jsx(WhaleArt, { frameIndex: frameIndex, width: FULL_WHALE_WIDTH }), _jsxs(Box, { flexDirection: "column", flexShrink: 1, children: [_jsxs(Text, { wrap: "truncate-end", children: [sweep('✦ dsh-TUI', t, wordmarkRGB, wordmarkShimmerRGB, 60), _jsx(Text, { dimColor: true, children: ' v' + VERSION })] }), bigDeepSeek.map((row, index) => (_jsx(Text, { wrap: "truncate-end", children: row }, `ds-${index}`))), bigHarness.map((row, index) => (_jsx(Text, { wrap: "truncate-end", children: row }, `h-${index}`))), _jsxs(Text, { wrap: "truncate-end", children: [model, effort !== undefined && _jsx(Text, { dimColor: true, children: ' · ' + capitalize(effort) + ' effort' })] }), _jsx(Text, { dimColor: true, wrap: "truncate-end", children: cwd }), _jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { dimColor: true, children: tr('logo-tip-prefix') }), getLang() === 'zh' ? randomTip.zh : randomTip.en, _jsx(Text, { dimColor: true, children: ' · /tips ' + tr('logo-tip-more') })] }), driftLine != null && (_jsxs(Text, { color: "warning", wrap: "wrap", children: ["\u26A0", ' ', tOr(`logo-drift-${driftLine.kind}`, `The dsh engine (${driftLine.versions.join(' / ')}) does not match the validated ${UPSTREAM_VALIDATED_VERSION}; reinstall via npm i -g @deepseek-ai/dsh@${UPSTREAM_VALIDATED_VERSION}.`, {
114
+ installed: driftLine.versions.join(' / '),
115
+ validated: UPSTREAM_VALIDATED_VERSION,
116
+ primary: UPSTREAM_VALIDATED_VERSION,
117
+ })] }))] })] }), _jsx(Box, { marginTop: 1, paddingLeft: welcomePad, children: _jsx(Text, { children: sweep(tagline, t, taglineRGB, FLASH, 60) }) })] }));
109
118
  }
@@ -3,7 +3,7 @@ import { type ScrollBoxHandle } from '../ui.js';
3
3
  import type { ChatRow } from '../dsh-adapter/channel.js';
4
4
  import type { DOMElement } from '../ink/dom.js';
5
5
  import type { ToolBackground } from '../tuiDisplayPrefs.js';
6
- export declare function MessageList({ rows, expanded, expandedRows, selectedId, onToggleRow, model, diffLayout, thinkingFold, toolBackground, showAll, onToggleAll, onLoadOlder, thinkingVisible, registerRowRef, scrollHandle, forceMountRowId, newSinceRowId, onUnseenCount, failureHintRowId, failureHint, }: {
6
+ export declare function MessageList({ rows, expanded, expandedRows, selectedId, onToggleRow, model, diffLayout, thinkingFold, toolBackground, activityFrames, showAll, onToggleAll, onLoadOlder, thinkingVisible, registerRowRef, scrollHandle, forceMountRowId, newSinceRowId, onUnseenCount, failureHintRowId, failureHint, }: {
7
7
  rows: readonly ChatRow[];
8
8
  expanded: boolean;
9
9
  expandedRows: ReadonlySet<number>;
@@ -16,6 +16,9 @@ export declare function MessageList({ rows, expanded, expandedRows, selectedId,
16
16
  thinkingFold?: 'preview' | 'full';
17
17
  /** Tool-card background treatment from the live channel settings. */
18
18
  toolBackground?: ToolBackground;
19
+ /** Working-activity preset name from the channel; drives the subagent
20
+ * card's running glyph so both indicators follow one setting. */
21
+ activityFrames?: string;
19
22
  showAll: boolean;
20
23
  onToggleAll: () => void;
21
24
  /** Restore folded-away older rows from the session log (CC-style "load
@@ -48,9 +51,10 @@ export declare function MessageList({ rows, expanded, expandedRows, selectedId,
48
51
  * welcome line. It scrolls away with the transcript once the conversation
49
52
  * fills the viewport (Claude Code shows its ✦ logo in the same slot).
50
53
  */
51
- export declare function LogoHeader({ model, effort, cwd, }: {
54
+ export declare function LogoHeader({ model, effort, cwd, whale, }: {
52
55
  model: string;
53
56
  effort?: string | undefined;
54
57
  cwd: string;
58
+ whale?: boolean;
55
59
  }): React.ReactNode;
56
60
  //# sourceMappingURL=MessageList.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/MessageList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAA8B,KAAK,eAAe,EAAE,MAAM,UAAU,CAAA;AAC3E,OAAO,KAAK,EAAE,OAAO,EAAyC,MAAM,2BAA2B,CAAA;AAC/F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAa/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AA2B3D,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,WAAW,EACX,KAAK,EACL,UAAmB,EACnB,YAAwB,EACxB,cAAuB,EACvB,OAAO,EACP,WAAW,EACX,WAAW,EACX,eAAsB,EACtB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,WAAW,GACZ,EAAE;IACD,IAAI,EAAE,SAAS,OAAO,EAAE,CAAA;IACxB,QAAQ,EAAE,OAAO,CAAA;IACjB,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACjC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACpC,KAAK,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;IACzC,mEAAmE;IACnE,YAAY,CAAC,EAAE,SAAS,GAAG,MAAM,CAAA;IACjC,qEAAqE;IACrE,cAAc,CAAC,EAAE,cAAc,CAAA;IAC/B,OAAO,EAAE,OAAO,CAAA;IAChB,WAAW,EAAE,MAAM,IAAI,CAAA;IACvB;2EACuE;IACvE,WAAW,CAAC,EAAE,MAAM,IAAI,CAAA;IACxB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,GAAG,IAAI,KAAK,IAAI,CAAA;IAC/D,oDAAoD;IACpD,YAAY,CAAC,EAAE,eAAe,GAAG,IAAI,CAAA;IACrC,4EAA4E;IAC5E,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B;8EAC0E;IAC1E,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,0EAA0E;IAC1E,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,qBA8VA;AAsRD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,MAAM,EACN,GAAG,GACJ,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,GAAG,EAAE,MAAM,CAAA;CACZ,GAAG,KAAK,CAAC,SAAS,CAMlB"}
1
+ {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/MessageList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAA8B,KAAK,eAAe,EAAE,MAAM,UAAU,CAAA;AAC3E,OAAO,KAAK,EAAE,OAAO,EAAsD,MAAM,2BAA2B,CAAA;AAC5G,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAe/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AA2B3D,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,WAAW,EACX,KAAK,EACL,UAAmB,EACnB,YAAwB,EACxB,cAAuB,EACvB,cAAc,EACd,OAAO,EACP,WAAW,EACX,WAAW,EACX,eAAsB,EACtB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,WAAW,GACZ,EAAE;IACD,IAAI,EAAE,SAAS,OAAO,EAAE,CAAA;IACxB,QAAQ,EAAE,OAAO,CAAA;IACjB,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACjC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACpC,KAAK,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;IACzC,mEAAmE;IACnE,YAAY,CAAC,EAAE,SAAS,GAAG,MAAM,CAAA;IACjC,qEAAqE;IACrE,cAAc,CAAC,EAAE,cAAc,CAAA;IAC/B;sEACkE;IAClE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,OAAO,EAAE,OAAO,CAAA;IAChB,WAAW,EAAE,MAAM,IAAI,CAAA;IACvB;2EACuE;IACvE,WAAW,CAAC,EAAE,MAAM,IAAI,CAAA;IACxB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,GAAG,IAAI,KAAK,IAAI,CAAA;IAC/D,oDAAoD;IACpD,YAAY,CAAC,EAAE,eAAe,GAAG,IAAI,CAAA;IACrC,4EAA4E;IAC5E,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B;8EAC0E;IAC1E,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,0EAA0E;IAC1E,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,qBAiWA;AA2SD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,MAAM,EACN,GAAG,EACH,KAAY,GACb,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB,GAAG,KAAK,CAAC,SAAS,CASlB"}
@@ -7,6 +7,8 @@ import { UserPromptMessage } from './messages/UserPromptMessage.js';
7
7
  import { AssistantTextMessage } from './messages/AssistantTextMessage.js';
8
8
  import { AssistantThinkingMessage } from './messages/AssistantThinkingMessage.js';
9
9
  import { AssistantToolUseMessage } from './messages/AssistantToolUseMessage.js';
10
+ import { SubagentMessage } from './Chat/SubagentMessage.js';
11
+ import { isMinimalMode } from '../minimalMode.js';
10
12
  import { InterruptedByUser } from './InterruptedByUser.js';
11
13
  import { LogoV2 } from './LogoV2.js';
12
14
  import { StreamingMarkdown } from './StreamingMarkdown.js';
@@ -37,7 +39,7 @@ const DEFAULT_ROW_HEIGHT = 2;
37
39
  /** Cold-start estimate of the header block above the rows; corrected by the
38
40
  * first layout measurement. */
39
41
  const DEFAULT_HEADER_LINES = 14;
40
- export function MessageList({ rows, expanded, expandedRows, selectedId, onToggleRow, model, diffLayout = 'auto', thinkingFold = 'preview', toolBackground = 'none', showAll, onToggleAll, onLoadOlder, thinkingVisible = true, registerRowRef, scrollHandle, forceMountRowId, newSinceRowId, onUnseenCount, failureHintRowId, failureHint, }) {
42
+ export function MessageList({ rows, expanded, expandedRows, selectedId, onToggleRow, model, diffLayout = 'auto', thinkingFold = 'preview', toolBackground = 'none', activityFrames, showAll, onToggleAll, onLoadOlder, thinkingVisible = true, registerRowRef, scrollHandle, forceMountRowId, newSinceRowId, onUnseenCount, failureHintRowId, failureHint, }) {
41
43
  const hiddenCount = rows.length - MAX_RENDERED_ROWS;
42
44
  // The thinking filter runs BEFORE virtualization so window indices line up.
43
45
  const visibleRows = (showAll || hiddenCount <= 0
@@ -332,10 +334,11 @@ export function MessageList({ rows, expanded, expandedRows, selectedId, onToggle
332
334
  // spacing; only the very first row of the whole list has none.
333
335
  const addMargin = margins.get(row.id) === true;
334
336
  const tool = row.tool;
335
- return (_jsx(MemoRow, { rowId: row.id, kind: row.kind, text: row.text, executionTarget: row.executionTarget, streaming: row.streaming === true, durationMs: row.durationMs, time: row.time, addMargin: addMargin, isSelected: selectedId === row.id, isExpanded: expandedRows.has(row.id), expanded: expanded, model: model, diffLayout: diffLayout, thinkingFold: thinkingFold, toolBackground: toolBackground, background: rowBackground(row.id), toolCallId: tool?.callId, toolName: tool?.name, toolArgsText: tool?.argsText, toolArgsFull: tool?.argsFull, toolStatus: tool?.status, toolResultText: tool?.resultText, toolResultFull: tool?.resultFull, toolErrorText: tool?.errorText, toolFootnote: failureHintRowId === row.id ? failureHint : undefined, toolCallView: tool?.callView, toolResultView: tool?.resultView, toolStartedAt: tool?.startedAt, toolDurationMs: tool?.durationMs, nowSec: tool?.status === 'running' ? nowSec : undefined, onToggleRow: onToggleRow, setRowRef: setRowRef }, row.id));
337
+ const subagent = row.kind === 'subagent' ? row.subagent : undefined;
338
+ return (_jsx(MemoRow, { rowId: row.id, kind: row.kind, text: row.text, executionTarget: row.executionTarget, streaming: row.streaming === true, durationMs: row.durationMs, time: row.time, addMargin: addMargin, isSelected: selectedId === row.id, isExpanded: expandedRows.has(row.id), expanded: expanded, model: model, diffLayout: diffLayout, thinkingFold: thinkingFold, toolBackground: toolBackground, activityFrames: activityFrames, background: rowBackground(row.id), toolCallId: tool?.callId, toolName: tool?.name, toolArgsText: tool?.argsText, toolArgsFull: tool?.argsFull, toolStatus: tool?.status, toolResultText: tool?.resultText, toolResultFull: tool?.resultFull, toolErrorText: tool?.errorText, toolFootnote: failureHintRowId === row.id ? failureHint : undefined, toolCallView: tool?.callView, toolResultView: tool?.resultView, toolStartedAt: tool?.startedAt, toolDurationMs: tool?.durationMs, nowSec: tool?.status === 'running' ? nowSec : undefined, subagent: subagent, onToggleRow: onToggleRow, setRowRef: setRowRef }, row.id));
336
339
  }), bottomPad > 0 && _jsx(Box, { height: bottomPad, flexShrink: 0 })] }));
337
340
  }
338
- function TranscriptRow({ rowId, kind, text, executionTarget, streaming, durationMs, time, addMargin, isSelected, isExpanded, expanded, model, diffLayout, thinkingFold, toolBackground, background, toolCallId, toolName, toolArgsText, toolArgsFull, toolStatus, toolResultText, toolResultFull, toolErrorText, toolFootnote, toolCallView, toolResultView, toolStartedAt, toolDurationMs, onToggleRow, setRowRef, }) {
341
+ function TranscriptRow({ rowId, kind, text, executionTarget, streaming, durationMs, time, addMargin, isSelected, isExpanded, expanded, model, diffLayout, thinkingFold, toolBackground, activityFrames, background, toolCallId, toolName, toolArgsText, toolArgsFull, toolStatus, toolResultText, toolResultFull, toolErrorText, toolFootnote, toolCallView, toolResultView, toolStartedAt, toolDurationMs, subagent, onToggleRow, setRowRef, }) {
339
342
  const ref = React.useCallback((el) => {
340
343
  setRowRef(rowId, el);
341
344
  }, [setRowRef, rowId]);
@@ -346,7 +349,7 @@ function TranscriptRow({ rowId, kind, text, executionTarget, streaming, duration
346
349
  case 'user':
347
350
  return (_jsx(Box, { flexDirection: "column", ref: ref, children: _jsx(UserPromptMessage, { text: text, addMargin: addMargin, isSelected: isSelected, onClick: onClick }) }));
348
351
  case 'assistant':
349
- return streaming ? (_jsxs(Box, { alignItems: "flex-start", flexDirection: "row", marginTop: addMargin ? 1 : 0, width: "100%", backgroundColor: background, children: [_jsx(Box, { minWidth: 2, children: _jsx(Text, { color: "text", children: "\u25CF" }) }), _jsx(Box, { flexDirection: "column", children: _jsx(StreamingMarkdown, { children: stripNarration(text) }) })] })) : (_jsxs(Box, { width: "100%", flexDirection: "column", backgroundColor: background, ref: ref, children: [expanded && (_jsx(Box, { flexDirection: "row", justifyContent: "flex-end", gap: 1, marginTop: 1, children: _jsx(MessageMetadata, { timestamp: time, model: model }) })), _jsx(AssistantTextMessage, { text: stripNarration(text), addMargin: addMargin, isSelected: isSelected, isExpanded: isExpanded, onClick: onClick })] }));
352
+ return streaming ? (_jsxs(Box, { alignItems: "flex-start", flexDirection: "row", marginTop: addMargin ? 1 : 0, width: "100%", backgroundColor: background, ref: ref, children: [_jsx(Box, { minWidth: 2, children: _jsx(Text, { color: "text", children: "\u25CF" }) }), _jsx(Box, { flexDirection: "column", children: _jsx(StreamingMarkdown, { children: stripNarration(text) }) })] })) : (_jsxs(Box, { width: "100%", flexDirection: "column", backgroundColor: background, ref: ref, children: [expanded && (_jsx(Box, { flexDirection: "row", justifyContent: "flex-end", gap: 1, marginTop: 1, children: _jsx(MessageMetadata, { timestamp: time, model: model }) })), _jsx(AssistantTextMessage, { text: stripNarration(text), addMargin: addMargin, isSelected: isSelected, isExpanded: isExpanded, onClick: onClick })] }));
350
353
  case 'reasoning':
351
354
  return (_jsx(Box, { flexDirection: "column", ref: ref, children: _jsx(AssistantThinkingMessage, { thinking: text, addMargin: addMargin, streaming: streaming, preview: streaming &&
352
355
  thinkingFold === 'preview' &&
@@ -396,6 +399,10 @@ function TranscriptRow({ rowId, kind, text, executionTarget, streaming, duration
396
399
  // text preview; Ctrl+O (global) or message-selection Enter reveals
397
400
  // the full summary.
398
401
  return (_jsx(Box, { marginTop: addMargin ? 1 : 0, paddingLeft: 2, backgroundColor: background, ref: ref, onClick: onClick, children: expanded || isExpanded ? (_jsx(Text, { dimColor: true, children: text })) : (_jsxs(Text, { dimColor: true, italic: true, children: ["\u2234 ", t('compact-summary-folded'), " \u00B7 ", compactPreview(text), ' ', t('hint-expand-ctrl-o')] })) }));
402
+ case 'subagent':
403
+ if (!subagent)
404
+ return null;
405
+ return (_jsx(Box, { flexDirection: "column", ref: ref, children: _jsx(SubagentMessage, { subagent: subagent, addMargin: addMargin, activityFrames: activityFrames, isExpanded: isExpanded, onClick: () => onToggleRow(rowId) }) }));
399
406
  }
400
407
  }
401
408
  /** Folded compact-summary preview: whitespace flattened, capped with an
@@ -412,6 +419,10 @@ const MemoRow = React.memo(TranscriptRow);
412
419
  * welcome line. It scrolls away with the transcript once the conversation
413
420
  * fills the viewport (Claude Code shows its ✦ logo in the same slot).
414
421
  */
415
- export function LogoHeader({ model, effort, cwd, }) {
416
- return (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: _jsx(LogoV2, { model: model, effort: effort, cwd: cwd }) }));
422
+ export function LogoHeader({ model, effort, cwd, whale = true, }) {
423
+ // Minimal mode drops the whole splash (whale art AND wordmark) — only the
424
+ // transcript and a bare status bar remain.
425
+ if (isMinimalMode())
426
+ return null;
427
+ return (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: _jsx(LogoV2, { model: model, effort: effort, cwd: cwd, whale: whale }) }));
417
428
  }
@@ -18,8 +18,8 @@ export function ModelPicker({ models, focusIndex, currentModel, }) {
18
18
  const { rows: terminalRows } = useTerminalSize();
19
19
  // 焦点窗口化按行预算:ListItem 带 description 时占 2 行(正文+描述,均
20
20
  // truncate 成单行),只数项数会把焦点裁出浮层(二次审查实证)。
21
- // 框架行:浮层预留 8 + Pane 2 + 标题 2 + 页脚 1 = 13。
22
- const { start, end } = listWindow(models.map(m => (m.description ? 2 : 1)), focusIndex, Math.max(terminalRows - 13, 2));
21
+ // 框架行:浮层预留 8 + Pane 2 + 标题 2 + 页脚 1 + 挂载包裹 marginTop 1 = 14。
22
+ const { start, end } = listWindow(models.map(m => (m.description ? 2 : 1)), focusIndex, Math.max(terminalRows - 14, 2));
23
23
  return (_jsxs(Pane, { color: "permission", children: [_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "remember", bold: true, children: t('picker-title-model') }) }), models.slice(start, end).map((model, index) => {
24
24
  const absoluteIndex = start + index;
25
25
  return (_jsxs(ListItem, { isFocused: absoluteIndex === focusIndex, isSelected: `${model.provider}/${model.id}` === currentModel, description: model.description, showScrollUp: absoluteIndex === start && start > 0, showScrollDown: absoluteIndex === end - 1 && end < models.length, children: [model.provider, " / ", model.name] }, `${model.provider}/${model.id}`));
@@ -49,7 +49,7 @@ export interface PromptInputProps {
49
49
  * the input spans multiple lines (history/command selection otherwise); the
50
50
  * visible window scrolls to keep the caret row on screen past
51
51
  * MAX_VISIBLE_LINES. Enter submits, backspace/delete edit, ←/→ move the
52
- * cursor, Tab completes the selected command, Ctrl+X opens the draft in the
52
+ * cursor, Tab completes the selected command, Ctrl+G opens the draft in the
53
53
  * external editor ($VISUAL/$EDITOR), Escape clears (or closes the help
54
54
  * menu), `?` toggles the help menu. Windows ConPTY pipelines deliver
55
55
  * whole lines with the Enter key lost: a trailing CR/LF in the input marks
@@ -1 +1 @@
1
- {"version":3,"file":"PromptInput.d.ts","sourceRoot":"","sources":["../../../src/components/PromptInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAazB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAkDxD;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,IAAI,OAAO,CAAA;IAClB,KAAK,IAAI,IAAI,CAAA;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAA;IAChB,0EAA0E;IAC1E,QAAQ,EAAE,OAAO,CAAA;IACjB,YAAY,IAAI,IAAI,CAAA;IACpB;;;OAGG;IACH,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAA;IACrD,6EAA6E;IAC7E,eAAe,EAAE,OAAO,CAAA;IACxB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,cAAc,CAAC,IAAI,IAAI,CAAA;IACvB,8EAA8E;IAC9E,eAAe,CAAC,IAAI,IAAI,CAAA;IACxB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;CACzD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,cAAc,EACd,eAAe,EACf,aAAa,GACd,EAAE,gBAAgB,qBA46BlB"}
1
+ {"version":3,"file":"PromptInput.d.ts","sourceRoot":"","sources":["../../../src/components/PromptInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAazB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAmDxD;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,IAAI,OAAO,CAAA;IAClB,KAAK,IAAI,IAAI,CAAA;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAA;IAChB,0EAA0E;IAC1E,QAAQ,EAAE,OAAO,CAAA;IACjB,YAAY,IAAI,IAAI,CAAA;IACpB;;;OAGG;IACH,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAA;IACrD,6EAA6E;IAC7E,eAAe,EAAE,OAAO,CAAA;IACxB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,cAAc,CAAC,IAAI,IAAI,CAAA;IACvB,8EAA8E;IAC9E,eAAe,CAAC,IAAI,IAAI,CAAA;IACxB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;CACzD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,cAAc,EACd,eAAe,EACf,aAAa,GACd,EAAE,gBAAgB,qBAo/BlB"}
@@ -12,9 +12,10 @@ import { useDeclaredCursor } from '../ink/hooks/use-declared-cursor.js';
12
12
  import { stringWidth } from '../ink/stringWidth.js';
13
13
  import { formatClipboardInsert, readClipboard } from '../utils/clipboard.js';
14
14
  import { editInExternalEditor } from '../utils/externalEditor.js';
15
- import { parseCommandName } from '../commands.js';
15
+ import { isHiddenCommandName, parseCommandName } from '../commands.js';
16
16
  import { appendHistory } from '../history.js';
17
17
  import { mentionAtCaret } from '../utils/mentions.js';
18
+ import { preserveSelection } from '../utils/fileSuggestions.js';
18
19
  import { isMod } from '../utils/modifiers.js';
19
20
  import { CommandSuggestions } from './CommandSuggestions.js';
20
21
  import { FileSuggestions } from './FileSuggestions.js';
@@ -77,7 +78,7 @@ const MAX_VISIBLE_LINES = 5;
77
78
  * the input spans multiple lines (history/command selection otherwise); the
78
79
  * visible window scrolls to keep the caret row on screen past
79
80
  * MAX_VISIBLE_LINES. Enter submits, backspace/delete edit, ←/→ move the
80
- * cursor, Tab completes the selected command, Ctrl+X opens the draft in the
81
+ * cursor, Tab completes the selected command, Ctrl+G opens the draft in the
81
82
  * external editor ($VISUAL/$EDITOR), Escape clears (or closes the help
82
83
  * menu), `?` toggles the help menu. Windows ConPTY pipelines deliver
83
84
  * whole lines with the Enter key lost: a trailing CR/LF in the input marks
@@ -140,7 +141,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
140
141
  const escTimerRef = React.useRef(null);
141
142
  /** True while a Ctrl+V clipboard read is in flight (ignore repeat keys). */
142
143
  const clipboardBusyRef = React.useRef(false);
143
- /** True while the external editor owns the terminal (Ctrl+X round-trip). */
144
+ /** True while the external editor owns the terminal (Ctrl+G round-trip). */
144
145
  const editorBusyRef = React.useRef(false);
145
146
  /** Enter dedupe window: cmd pipelines can deliver one Enter as `\r`+`\n`. */
146
147
  const lastEnterAtRef = React.useRef(0);
@@ -151,6 +152,11 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
151
152
  };
152
153
  }, []);
153
154
  const { columns, rows: terminalRows } = useTerminalSize();
155
+ const helpScrollRef = React.useRef(null);
156
+ // OverlayAbove reserves six terminal rows for the composer/status chrome;
157
+ // the help block also keeps one row below it. Its own final row is a
158
+ // persistent navigation hint, leaving the remainder to ScrollBox.
159
+ const helpViewportHeight = Math.max(3, Math.max(terminalRows - 6, 4) - 1);
154
160
  const suggestions = value.startsWith('/') ? channel.commandCompletions(value) : [];
155
161
  const overlayOpen = suggestions.length > 0 &&
156
162
  !helpOpen &&
@@ -160,29 +166,31 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
160
166
  // CARET, so `@` works mid-message (`看看 @src/a.ts 这个`), not only when it
161
167
  // is the input's first character. The cwd listing loads when the trigger
162
168
  // appears.
163
- const [fileList, setFileList] = React.useState([]);
169
+ const [fileMatches, setFileMatches] = React.useState([]);
164
170
  const [fileSelected, setFileSelected] = React.useState(0);
165
171
  const mention = mentionAtCaret(value, cursor);
166
172
  const atTrigger = mention !== undefined;
173
+ const fileRequestId = React.useRef(0);
174
+ const selectedFile = fileMatches[fileSelected];
167
175
  React.useEffect(() => {
168
- if (atTrigger) {
169
- void channel.listFiles().then(setFileList);
170
- }
171
- }, [atTrigger, channel]);
172
- const atRest = (mention?.query ?? '').toLowerCase();
173
- // Match the relative path prefix OR the basename (CC's IDE suggestions do
174
- // both): `@src/ink` and `@ink` both find `src/ink/Box.js`.
175
- const fileMatches = atTrigger
176
- ? fileList.filter(file => {
177
- const lower = file.toLowerCase();
178
- if (lower.startsWith(atRest))
179
- return true;
180
- if (atRest.includes('/'))
181
- return false;
182
- const base = lower.split('/').pop() ?? '';
183
- return base.startsWith(atRest);
184
- })
185
- : [];
176
+ const requestId = ++fileRequestId.current;
177
+ if (!mention) {
178
+ setFileMatches([]);
179
+ setFileSelected(0);
180
+ return;
181
+ }
182
+ const previous = selectedFile;
183
+ // Deps key on `mention.query` (and trigger on/off) only: cursor movement
184
+ // within the same token must NOT refetch, and `selectedFile`/`fileSelected`
185
+ // are read as their render-time values only to seed selection preservation.
186
+ // eslint-disable-next-line react-hooks/exhaustive-deps
187
+ void channel.listFileCandidates(mention.query, { topK: 50 }).then(next => {
188
+ if (requestId !== fileRequestId.current)
189
+ return;
190
+ setFileMatches(next);
191
+ setFileSelected(preserveSelection(previous, next, fileSelected));
192
+ });
193
+ }, [channel, mention?.query, atTrigger]);
186
194
  // Esc dismisses the overlay for the token being edited (it reopens once the
187
195
  // text changes); it must NOT clear a mid-message input.
188
196
  const fileEscRef = React.useRef(-1);
@@ -205,11 +213,12 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
205
213
  * directory inserts `@dir/` without a trailing space so completion
206
214
  * continues into it; a file completes the token with a space.
207
215
  */
208
- const acceptFile = (file) => {
216
+ const acceptFile = (candidate) => {
209
217
  if (!mention)
210
218
  return;
219
+ const file = candidate.path;
211
220
  const body = /\s/.test(file) ? `@"${file}"` : `@${file}`;
212
- const insert = file.endsWith('/') ? body : `${body} `;
221
+ const insert = candidate.kind === 'directory' ? body : `${body} `;
213
222
  const next = value.slice(0, mention.start) + insert + value.slice(mention.end);
214
223
  setInput(next, mention.start + insert.length);
215
224
  setFileSelected(0);
@@ -317,10 +326,12 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
317
326
  channel.notify(t('input-interrupt-immediate'), { timeoutMs: 2500 });
318
327
  };
319
328
  /**
320
- * Execute a slash command (built-in or plugin-registered) when the input
321
- * resolves to one: the name parses as the first token so `/plan off`
322
- * dispatches `plan` with its argument text, and the merged command list
323
- * (locals + registry) decides whether the line is a command at all.
329
+ * Execute a slash command (built-in, plugin-registered, or hidden) when
330
+ * the input resolves to one: the name parses as the first token so
331
+ * `/plan off` dispatches `plan` with its argument text, and the merged
332
+ * command list (locals + registry) decides whether the line is a command
333
+ * at all. Hidden commands are recognized even though they are intentionally
334
+ * absent from the suggestion/help catalogs.
324
335
  */
325
336
  const tryRunCommand = (text) => {
326
337
  if (!text.startsWith('/'))
@@ -328,7 +339,8 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
328
339
  const parsed = parseCommandName(text);
329
340
  if (parsed === undefined)
330
341
  return false;
331
- const known = channel.commandList.some(command => command.name === parsed.name);
342
+ const known = channel.commandList.some(command => command.name === parsed.name)
343
+ || isHiddenCommandName(parsed.name);
332
344
  if (!known)
333
345
  return false;
334
346
  const handled = onRunCommand(parsed.name, parsed.rawInput);
@@ -451,14 +463,22 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
451
463
  });
452
464
  return;
453
465
  }
454
- // Ctrl+X / Cmd+X: edit the current draft in $VISUAL/$EDITOR (issue #123,
466
+ // Help is modal for modified keys and every Enter variant. Ctrl+V above
467
+ // is the intentional exception: paste closes Help and inserts visibly.
468
+ // Swallow here before editor/submit/interrupt branches can mutate hidden
469
+ // composer or working-turn state; plain typing still dismisses Help below.
470
+ if (helpOpen && !key.escape && (key.ctrl || key.meta || key.super || key.return || input.includes('\n') || input.includes('\r'))) {
471
+ event.stopImmediatePropagation();
472
+ return;
473
+ }
474
+ // Ctrl+G: edit the current draft in $VISUAL/$EDITOR (issue #123,
455
475
  // readline's edit-and-execute-command). The draft is written to a temp
456
476
  // file, the terminal is handed to the editor (Ink's alt-screen handoff),
457
477
  // and the saved text replaces the input when it differs. The util maps
458
478
  // every failure to an outcome, but the catch/finally here is the hard
459
479
  // guarantee: a rejected promise must never kill the process, and the
460
- // busy flag must always clear or Ctrl+X stays locked forever.
461
- if (isMod(key) && input === 'x') {
480
+ // busy flag must always clear or Ctrl+G stays locked forever.
481
+ if (key.ctrl && input === 'g') {
462
482
  editorBusyRef.current = true;
463
483
  void (async () => {
464
484
  try {
@@ -521,10 +541,13 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
521
541
  }
522
542
  if (channel.working && value.trim() !== '') {
523
543
  // CC's immediate-command semantics: /btw is exempt from steering —
524
- // the side question never interrupts the running turn. Every other
525
- // input keeps the steer behavior so /new /model etc. stay idle-only.
544
+ // the side question never interrupts the running turn. Hidden
545
+ // UI-only easter eggs (e.g. /deepseek) are also safe to run while
546
+ // streaming. Every other input keeps the steer behavior so /new
547
+ // /model etc. stay idle-only.
526
548
  const parsed = value.startsWith('/') ? parseCommandName(value) : undefined;
527
- if (parsed?.name === 'btw' && channel.commandList.some(c => c.name === 'btw')) {
549
+ if (parsed !== undefined && ((parsed.name === 'btw' && channel.commandList.some(c => c.name === 'btw'))
550
+ || isHiddenCommandName(parsed.name))) {
528
551
  tryRunCommand(value);
529
552
  return;
530
553
  }
@@ -583,6 +606,12 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
583
606
  handleEnter();
584
607
  return;
585
608
  }
609
+ // Help is modal over the composer. Backtab must not cycle the session
610
+ // mode invisibly behind it, and plain Tab has no Help action.
611
+ if (helpOpen && key.tab) {
612
+ event.stopImmediatePropagation();
613
+ return;
614
+ }
586
615
  // Shift+Tab cycles the configured session modes (default: 默认 →
587
616
  // 计划模式 → 完全访问; each mode bundles plan/sandbox/approval atoms —
588
617
  // see the `modes` config). Must precede the plain-Tab arms — the parser
@@ -609,6 +638,41 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
609
638
  queueSend(value);
610
639
  return;
611
640
  }
641
+ // Help is a viewport, not prompt history. It deliberately owns every
642
+ // vertical navigation event while visible; otherwise Up/Down silently
643
+ // walk the input history and the clipped command rows remain unreachable.
644
+ if (helpOpen) {
645
+ const page = Math.max(1, helpViewportHeight - 2);
646
+ if (key.upArrow || key.wheelUp) {
647
+ helpScrollRef.current?.scrollBy(key.wheelUp ? -3 : -1);
648
+ event.stopImmediatePropagation();
649
+ return;
650
+ }
651
+ if (key.downArrow || key.wheelDown) {
652
+ helpScrollRef.current?.scrollBy(key.wheelDown ? 3 : 1);
653
+ event.stopImmediatePropagation();
654
+ return;
655
+ }
656
+ if (key.pageUp || key.pageDown) {
657
+ helpScrollRef.current?.scrollBy(key.pageUp ? -page : page);
658
+ event.stopImmediatePropagation();
659
+ return;
660
+ }
661
+ if (key.home) {
662
+ helpScrollRef.current?.scrollTo(0);
663
+ event.stopImmediatePropagation();
664
+ return;
665
+ }
666
+ if (key.end) {
667
+ // Use a deliberately oversized absolute target rather than the
668
+ // sticky-bottom path: compact Help may still be measuring nested
669
+ // sections in this commit, while ScrollBox's render clamp resolves
670
+ // the target to the exact current maximum without a follow-up frame.
671
+ helpScrollRef.current?.scrollTo(Number.MAX_SAFE_INTEGER);
672
+ event.stopImmediatePropagation();
673
+ return;
674
+ }
675
+ }
612
676
  if (key.meta && key.upArrow) {
613
677
  // Alt+Up: pull the last pending message back for editing (pi/Codex).
614
678
  pullBackLast();
@@ -883,7 +947,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
883
947
  // 的 style.position,常驻浮层 + 移除普通子节点不会触发 blit 解毒,被
884
948
  // 覆盖的转录行会留空(见 Chat.tsx dialogOverlayOpen 注释)。
885
949
  const floatersOpen = helpOpen || channel.pending.length > 0 || fileOverlayOpen || overlayOpen;
886
- return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [floatersOpen && (_jsxs(OverlayAbove, { maxHeight: Math.max(terminalRows - 6, 4), children: [helpOpen && (_jsx(Box, { marginBottom: 1, children: _jsx(HelpMenu, { commands: channel.commandList }) })), channel.pending.length > 0 && (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, paddingBottom: 1, children: [channel.pending.some(item => item.placement === 'steer') && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: ["\u26A1 ", t('input-pending-steer-label')] }), channel.pending
950
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [floatersOpen && (_jsxs(OverlayAbove, { maxHeight: Math.max(terminalRows - 6, 4), children: [helpOpen && (_jsx(Box, { marginBottom: 1, children: _jsx(HelpMenu, { commands: channel.commandList, viewportHeight: helpViewportHeight, viewportWidth: columns, scrollRef: helpScrollRef }) })), !helpOpen && channel.pending.length > 0 && (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, paddingBottom: 1, children: [channel.pending.some(item => item.placement === 'steer') && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: ["\u26A1 ", t('input-pending-steer-label')] }), channel.pending
887
951
  .filter(item => item.placement === 'steer')
888
952
  .map(item => (_jsxs(Text, { dimColor: true, wrap: "truncate", children: [' ', "\u21B3 ", item.text] }, item.id)))] })), channel.pending.some(item => item.placement === 'followup') && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: ["\u23F3 ", t('input-pending-queue-label')] }), channel.pending
889
953
  .filter(item => item.placement === 'followup')
@@ -37,8 +37,8 @@ function sourceLabel(source) {
37
37
  export function SkillsPicker({ skills, focusIndex, }) {
38
38
  const { rows: terminalRows } = useTerminalSize();
39
39
  // 每项恒占 2 行(正文 + 来源/简述描述行,均 truncate 成单行)。
40
- // 框架行:浮层预留 8 + Pane 2 + 标题 2 + 页脚 1 = 13(ModelPicker 同款)。
41
- const { start, end } = listWindow(skills.map(() => 2), focusIndex, Math.max(terminalRows - 13, 2));
40
+ // 框架行:浮层预留 8 + Pane 2 + 标题 2 + 页脚 1 + 挂载包裹 marginTop 1 = 14(ModelPicker 同款)。
41
+ const { start, end } = listWindow(skills.map(() => 2), focusIndex, Math.max(terminalRows - 14, 2));
42
42
  return (_jsxs(Pane, { color: "permission", children: [_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "remember", bold: true, children: t('picker-title-skills') }) }), skills.length === 0 ? (_jsx(Text, { dimColor: true, children: t('skills-empty') })) : (skills.slice(start, end).map((skill, index) => {
43
43
  const absoluteIndex = start + index;
44
44
  return (_jsx(ListItem, { isFocused: absoluteIndex === focusIndex, description: `${sourceLabel(skill.source)}${skill.description === '' ? '' : ` · ${skill.description}`}`, showScrollUp: absoluteIndex === start && start > 0, showScrollDown: absoluteIndex === end - 1 && end < skills.length, children: skill.userInvocable ? `/${skill.name}` : skill.name }, skill.name));
@@ -1,20 +1,8 @@
1
1
  import React from 'react';
2
2
  import type { ToolFileDiff } from '../dsh-adapter/channel.js';
3
- import { type CliHighlight } from '../cc/cliHighlight.js';
4
3
  export { chalkFromToken } from '../cc/syntaxTheme.js';
4
+ export { parseAnsiRuns, highlightLines } from '../cc/syntaxRuns.js';
5
5
  import type { ToolBackground } from '../tuiDisplayPrefs.js';
6
- /** Parse a cli-highlight ANSI string into colored runs, preserving open
7
- * spans across embedded newlines (multi-line strings/comments). Handles
8
- * truecolor 38;2, 256-color 38;5 (tmux / FORCE_COLOR=2, issue #250 P1-1),
9
- * and single-code 16-color SGR. */
10
- export declare function parseAnsiRuns(text: string): {
11
- text: string;
12
- color?: string;
13
- }[];
14
- export declare function highlightLines(text: string, language: string | undefined, hl: CliHighlight | null, chTheme: Record<string, (text: string) => string> | undefined, themeSig: string): readonly {
15
- text: string;
16
- color?: string;
17
- }[][] | undefined;
18
6
  export declare function SplitDiffView({ diffs, width, maxRows, verbose, toolBackground, }: {
19
7
  readonly diffs: readonly ToolFileDiff[];
20
8
  /** Content width available to the whole two-pane block (divider included). */
@@ -1 +1 @@
1
- {"version":3,"file":"SplitDiffView.d.ts","sourceRoot":"","sources":["../../../src/components/SplitDiffView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAKzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAE7D,OAAO,EAA0B,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAIjF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAGrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAgE3D;;;oCAGoC;AACpC,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,CAuB9E;AAuBD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,EAAE,EAAE,YAAY,GAAG,IAAI,EACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,SAAS,EAC7D,QAAQ,EAAE,MAAM,GACf,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,GAAG,SAAS,CAe3D;AAsOD,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,KAAK,EACL,OAAO,EACP,OAAO,EACP,cAAuB,GACxB,EAAE;IACD,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAA;IACvC,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAA;CACzC,GAAG,KAAK,CAAC,SAAS,CAiGlB"}
1
+ {"version":3,"file":"SplitDiffView.d.ts","sourceRoot":"","sources":["../../../src/components/SplitDiffView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAIzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAO7D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAGnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAuR3D,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,KAAK,EACL,OAAO,EACP,OAAO,EACP,cAAuB,GACxB,EAAE;IACD,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAA;IACvC,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAA;CACzC,GAAG,KAAK,CAAC,SAAS,CAkFlB"}