@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.
- package/README.md +15 -5
- package/bin/dsh-tui.js +269 -218
- package/cordis.patch.yml +48 -22
- package/lib/types/cc/figures.d.ts.map +1 -1
- package/lib/types/cc/figures.js +14 -13
- package/lib/types/cc/syntaxRuns.d.ts +16 -0
- package/lib/types/cc/syntaxRuns.d.ts.map +1 -0
- package/lib/types/cc/syntaxRuns.js +79 -0
- package/lib/types/commands.d.ts +14 -0
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +20 -1
- package/lib/types/components/Chat/SubagentMessage.d.ts +19 -0
- package/lib/types/components/Chat/SubagentMessage.d.ts.map +1 -0
- package/lib/types/components/Chat/SubagentMessage.js +75 -0
- package/lib/types/components/EffortInputBorder.d.ts.map +1 -1
- package/lib/types/components/EffortInputBorder.js +2 -2
- package/lib/types/components/EffortTierBadge.js +1 -1
- package/lib/types/components/FileSuggestions.d.ts +8 -4
- package/lib/types/components/FileSuggestions.d.ts.map +1 -1
- package/lib/types/components/FileSuggestions.js +22 -12
- package/lib/types/components/HelpMenu.d.ts +8 -1
- package/lib/types/components/HelpMenu.d.ts.map +1 -1
- package/lib/types/components/HelpMenu.js +15 -4
- package/lib/types/components/LogoV2.d.ts +7 -1
- package/lib/types/components/LogoV2.d.ts.map +1 -1
- package/lib/types/components/LogoV2.js +13 -4
- package/lib/types/components/MessageList.d.ts +6 -2
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +17 -6
- package/lib/types/components/ModelPicker.js +2 -2
- package/lib/types/components/PromptInput.d.ts +1 -1
- package/lib/types/components/PromptInput.d.ts.map +1 -1
- package/lib/types/components/PromptInput.js +100 -36
- package/lib/types/components/SkillsPicker.js +2 -2
- package/lib/types/components/SplitDiffView.d.ts +1 -13
- package/lib/types/components/SplitDiffView.d.ts.map +1 -1
- package/lib/types/components/SplitDiffView.js +6 -105
- package/lib/types/components/SubagentCard.d.ts +9 -0
- package/lib/types/components/SubagentCard.d.ts.map +1 -0
- package/lib/types/components/SubagentCard.js +26 -0
- package/lib/types/components/SubagentDashboard.d.ts +13 -0
- package/lib/types/components/SubagentDashboard.d.ts.map +1 -0
- package/lib/types/components/SubagentDashboard.js +50 -0
- package/lib/types/components/SubagentDetailScene.d.ts +16 -0
- package/lib/types/components/SubagentDetailScene.d.ts.map +1 -0
- package/lib/types/components/SubagentDetailScene.js +141 -0
- package/lib/types/components/SyntaxText.d.ts +15 -0
- package/lib/types/components/SyntaxText.d.ts.map +1 -0
- package/lib/types/components/SyntaxText.js +34 -0
- package/lib/types/components/ThinkingToggle.d.ts +3 -10
- package/lib/types/components/ThinkingToggle.d.ts.map +1 -1
- package/lib/types/components/ThinkingToggle.js +4 -9
- package/lib/types/components/design-system/ListItem.d.ts.map +1 -1
- package/lib/types/components/design-system/ListItem.js +1 -1
- package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantThinkingMessage.js +18 -7
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts +2 -0
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantToolUseMessage.js +42 -6
- package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
- package/lib/types/components/messages/UserPromptMessage.js +15 -2
- package/lib/types/dsh-adapter/channel.d.ts +64 -3
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +387 -37
- package/lib/types/dsh-adapter/contract.d.ts +67 -4
- package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
- package/lib/types/dsh-adapter/contract.js +149 -15
- package/lib/types/dsh-adapter/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.js +6 -6
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +38 -0
- package/lib/types/dsh-adapter/shortcuts.js +3 -3
- package/lib/types/dsh-adapter/subagents.d.ts +87 -0
- package/lib/types/dsh-adapter/subagents.d.ts.map +1 -0
- package/lib/types/dsh-adapter/subagents.js +204 -0
- package/lib/types/herdr.d.ts +28 -0
- package/lib/types/herdr.d.ts.map +1 -0
- package/lib/types/herdr.js +133 -0
- package/lib/types/i18n.d.ts +233 -32
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +87 -16
- package/lib/types/ink/ink.d.ts +4 -0
- package/lib/types/ink/ink.d.ts.map +1 -1
- package/lib/types/ink/ink.js +84 -38
- package/lib/types/ink/render-node-to-output.d.ts +6 -3
- package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
- package/lib/types/ink/render-node-to-output.js +33 -7
- package/lib/types/ink/selection.d.ts +29 -4
- package/lib/types/ink/selection.d.ts.map +1 -1
- package/lib/types/ink/selection.js +41 -5
- package/lib/types/minimalMode.d.ts +13 -0
- package/lib/types/minimalMode.d.ts.map +1 -0
- package/lib/types/minimalMode.js +17 -0
- package/lib/types/screens/Chat.d.ts +1 -1
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +94 -43
- package/lib/types/screens/StatusLine.d.ts.map +1 -1
- package/lib/types/screens/StatusLine.js +6 -2
- package/lib/types/screens/TrajectoryScene.d.ts.map +1 -1
- package/lib/types/screens/TrajectoryScene.js +4 -2
- package/lib/types/theme.d.ts +8 -0
- package/lib/types/theme.d.ts.map +1 -1
- package/lib/types/theme.js +38 -14
- package/lib/types/tips.d.ts.map +1 -1
- package/lib/types/tips.js +25 -7
- package/lib/types/update.d.ts +18 -0
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +73 -2
- package/lib/types/utils/execFileNoThrow.d.ts +3 -20
- package/lib/types/utils/execFileNoThrow.d.ts.map +1 -1
- package/lib/types/utils/execFileNoThrow.js +53 -32
- package/lib/types/utils/externalEditor.d.ts +12 -11
- package/lib/types/utils/externalEditor.d.ts.map +1 -1
- package/lib/types/utils/externalEditor.js +11 -11
- package/lib/types/utils/fileSuggestions.d.ts +19 -0
- package/lib/types/utils/fileSuggestions.d.ts.map +1 -0
- package/lib/types/utils/fileSuggestions.js +40 -0
- package/lib/types/utils/mentions.d.ts.map +1 -1
- package/lib/types/utils/mentions.js +3 -1
- 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;
|
|
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') })] })
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
22
|
-
const { start, end } = listWindow(models.map(m => (m.description ? 2 : 1)), focusIndex, Math.max(terminalRows -
|
|
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+
|
|
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;
|
|
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+
|
|
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+
|
|
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 [
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (
|
|
181
|
-
return
|
|
182
|
-
|
|
183
|
-
|
|
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 = (
|
|
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 =
|
|
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
|
|
321
|
-
* resolves to one: the name parses as the first token so
|
|
322
|
-
* dispatches `plan` with its argument text, and the merged
|
|
323
|
-
* (locals + registry) decides whether the line is a command
|
|
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
|
-
//
|
|
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+
|
|
461
|
-
if (
|
|
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.
|
|
525
|
-
//
|
|
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
|
|
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 =
|
|
41
|
-
const { start, end } = listWindow(skills.map(() => 2), focusIndex, Math.max(terminalRows -
|
|
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;
|
|
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"}
|