@deepseek-harness-tui/dsh-tui 0.8.6 → 0.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -3
- package/bin/dsh-tui.js +269 -218
- package/cordis.patch.yml +48 -22
- package/lib/types/cc/figures.d.ts.map +1 -1
- package/lib/types/cc/figures.js +14 -13
- package/lib/types/cc/syntaxRuns.d.ts +16 -0
- package/lib/types/cc/syntaxRuns.d.ts.map +1 -0
- package/lib/types/cc/syntaxRuns.js +79 -0
- package/lib/types/commands.d.ts +14 -0
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +20 -1
- package/lib/types/components/Chat/SubagentMessage.d.ts +19 -0
- package/lib/types/components/Chat/SubagentMessage.d.ts.map +1 -0
- package/lib/types/components/Chat/SubagentMessage.js +75 -0
- package/lib/types/components/EffortInputBorder.d.ts.map +1 -1
- package/lib/types/components/EffortInputBorder.js +2 -2
- package/lib/types/components/EffortTierBadge.js +1 -1
- package/lib/types/components/HelpMenu.d.ts +8 -1
- package/lib/types/components/HelpMenu.d.ts.map +1 -1
- package/lib/types/components/HelpMenu.js +15 -4
- package/lib/types/components/LogoV2.d.ts +3 -1
- package/lib/types/components/LogoV2.d.ts.map +1 -1
- package/lib/types/components/LogoV2.js +2 -2
- package/lib/types/components/MessageList.d.ts +6 -2
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +17 -6
- package/lib/types/components/ModelPicker.js +2 -2
- package/lib/types/components/PromptInput.d.ts +1 -1
- package/lib/types/components/PromptInput.d.ts.map +1 -1
- package/lib/types/components/PromptInput.js +75 -15
- package/lib/types/components/SkillsPicker.js +2 -2
- package/lib/types/components/SplitDiffView.d.ts +1 -13
- package/lib/types/components/SplitDiffView.d.ts.map +1 -1
- package/lib/types/components/SplitDiffView.js +6 -105
- package/lib/types/components/SubagentCard.d.ts +9 -0
- package/lib/types/components/SubagentCard.d.ts.map +1 -0
- package/lib/types/components/SubagentCard.js +26 -0
- package/lib/types/components/SubagentDashboard.d.ts +13 -0
- package/lib/types/components/SubagentDashboard.d.ts.map +1 -0
- package/lib/types/components/SubagentDashboard.js +50 -0
- package/lib/types/components/SubagentDetailScene.d.ts +16 -0
- package/lib/types/components/SubagentDetailScene.d.ts.map +1 -0
- package/lib/types/components/SubagentDetailScene.js +141 -0
- package/lib/types/components/SyntaxText.d.ts +15 -0
- package/lib/types/components/SyntaxText.d.ts.map +1 -0
- package/lib/types/components/SyntaxText.js +34 -0
- package/lib/types/components/ThinkingToggle.d.ts +3 -10
- package/lib/types/components/ThinkingToggle.d.ts.map +1 -1
- package/lib/types/components/ThinkingToggle.js +4 -9
- package/lib/types/components/design-system/ListItem.d.ts.map +1 -1
- package/lib/types/components/design-system/ListItem.js +1 -1
- package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantThinkingMessage.js +18 -7
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts +2 -0
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantToolUseMessage.js +42 -6
- package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
- package/lib/types/components/messages/UserPromptMessage.js +15 -2
- package/lib/types/dsh-adapter/channel.d.ts +53 -2
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +294 -7
- package/lib/types/dsh-adapter/contract.d.ts +27 -4
- package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
- package/lib/types/dsh-adapter/contract.js +56 -10
- package/lib/types/dsh-adapter/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.js +9 -3
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +38 -0
- package/lib/types/dsh-adapter/shortcuts.js +3 -3
- package/lib/types/dsh-adapter/subagents.d.ts +87 -0
- package/lib/types/dsh-adapter/subagents.d.ts.map +1 -0
- package/lib/types/dsh-adapter/subagents.js +204 -0
- package/lib/types/herdr.d.ts +28 -0
- package/lib/types/herdr.d.ts.map +1 -0
- package/lib/types/herdr.js +133 -0
- package/lib/types/i18n.d.ts +215 -31
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +65 -14
- package/lib/types/ink/ink.d.ts +4 -0
- package/lib/types/ink/ink.d.ts.map +1 -1
- package/lib/types/ink/ink.js +46 -15
- package/lib/types/minimalMode.d.ts +13 -0
- package/lib/types/minimalMode.d.ts.map +1 -0
- package/lib/types/minimalMode.js +17 -0
- package/lib/types/screens/Chat.d.ts +1 -1
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +94 -43
- package/lib/types/screens/StatusLine.d.ts.map +1 -1
- package/lib/types/screens/StatusLine.js +6 -2
- package/lib/types/screens/TrajectoryScene.d.ts.map +1 -1
- package/lib/types/screens/TrajectoryScene.js +4 -2
- package/lib/types/theme.d.ts +8 -0
- package/lib/types/theme.d.ts.map +1 -1
- package/lib/types/theme.js +38 -14
- package/lib/types/tips.js +3 -3
- package/lib/types/update.d.ts +18 -0
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +73 -2
- package/lib/types/utils/execFileNoThrow.d.ts +3 -20
- package/lib/types/utils/execFileNoThrow.d.ts.map +1 -1
- package/lib/types/utils/execFileNoThrow.js +53 -32
- package/lib/types/utils/externalEditor.d.ts +12 -11
- package/lib/types/utils/externalEditor.d.ts.map +1 -1
- package/lib/types/utils/externalEditor.js +11 -11
- package/package.json +64 -58
|
@@ -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;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,
|
|
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,qBAg/BlB"}
|
|
@@ -12,7 +12,7 @@ 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
18
|
import { isMod } from '../utils/modifiers.js';
|
|
@@ -77,7 +77,7 @@ const MAX_VISIBLE_LINES = 5;
|
|
|
77
77
|
* the input spans multiple lines (history/command selection otherwise); the
|
|
78
78
|
* visible window scrolls to keep the caret row on screen past
|
|
79
79
|
* MAX_VISIBLE_LINES. Enter submits, backspace/delete edit, ←/→ move the
|
|
80
|
-
* cursor, Tab completes the selected command, Ctrl+
|
|
80
|
+
* cursor, Tab completes the selected command, Ctrl+G opens the draft in the
|
|
81
81
|
* external editor ($VISUAL/$EDITOR), Escape clears (or closes the help
|
|
82
82
|
* menu), `?` toggles the help menu. Windows ConPTY pipelines deliver
|
|
83
83
|
* whole lines with the Enter key lost: a trailing CR/LF in the input marks
|
|
@@ -140,7 +140,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
140
140
|
const escTimerRef = React.useRef(null);
|
|
141
141
|
/** True while a Ctrl+V clipboard read is in flight (ignore repeat keys). */
|
|
142
142
|
const clipboardBusyRef = React.useRef(false);
|
|
143
|
-
/** True while the external editor owns the terminal (Ctrl+
|
|
143
|
+
/** True while the external editor owns the terminal (Ctrl+G round-trip). */
|
|
144
144
|
const editorBusyRef = React.useRef(false);
|
|
145
145
|
/** Enter dedupe window: cmd pipelines can deliver one Enter as `\r`+`\n`. */
|
|
146
146
|
const lastEnterAtRef = React.useRef(0);
|
|
@@ -151,6 +151,11 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
151
151
|
};
|
|
152
152
|
}, []);
|
|
153
153
|
const { columns, rows: terminalRows } = useTerminalSize();
|
|
154
|
+
const helpScrollRef = React.useRef(null);
|
|
155
|
+
// OverlayAbove reserves six terminal rows for the composer/status chrome;
|
|
156
|
+
// the help block also keeps one row below it. Its own final row is a
|
|
157
|
+
// persistent navigation hint, leaving the remainder to ScrollBox.
|
|
158
|
+
const helpViewportHeight = Math.max(3, Math.max(terminalRows - 6, 4) - 1);
|
|
154
159
|
const suggestions = value.startsWith('/') ? channel.commandCompletions(value) : [];
|
|
155
160
|
const overlayOpen = suggestions.length > 0 &&
|
|
156
161
|
!helpOpen &&
|
|
@@ -317,10 +322,12 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
317
322
|
channel.notify(t('input-interrupt-immediate'), { timeoutMs: 2500 });
|
|
318
323
|
};
|
|
319
324
|
/**
|
|
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
|
|
325
|
+
* Execute a slash command (built-in, plugin-registered, or hidden) when
|
|
326
|
+
* the input resolves to one: the name parses as the first token so
|
|
327
|
+
* `/plan off` dispatches `plan` with its argument text, and the merged
|
|
328
|
+
* command list (locals + registry) decides whether the line is a command
|
|
329
|
+
* at all. Hidden commands are recognized even though they are intentionally
|
|
330
|
+
* absent from the suggestion/help catalogs.
|
|
324
331
|
*/
|
|
325
332
|
const tryRunCommand = (text) => {
|
|
326
333
|
if (!text.startsWith('/'))
|
|
@@ -328,7 +335,8 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
328
335
|
const parsed = parseCommandName(text);
|
|
329
336
|
if (parsed === undefined)
|
|
330
337
|
return false;
|
|
331
|
-
const known = channel.commandList.some(command => command.name === parsed.name)
|
|
338
|
+
const known = channel.commandList.some(command => command.name === parsed.name)
|
|
339
|
+
|| isHiddenCommandName(parsed.name);
|
|
332
340
|
if (!known)
|
|
333
341
|
return false;
|
|
334
342
|
const handled = onRunCommand(parsed.name, parsed.rawInput);
|
|
@@ -451,14 +459,22 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
451
459
|
});
|
|
452
460
|
return;
|
|
453
461
|
}
|
|
454
|
-
//
|
|
462
|
+
// Help is modal for modified keys and every Enter variant. Ctrl+V above
|
|
463
|
+
// is the intentional exception: paste closes Help and inserts visibly.
|
|
464
|
+
// Swallow here before editor/submit/interrupt branches can mutate hidden
|
|
465
|
+
// composer or working-turn state; plain typing still dismisses Help below.
|
|
466
|
+
if (helpOpen && !key.escape && (key.ctrl || key.meta || key.super || key.return || input.includes('\n') || input.includes('\r'))) {
|
|
467
|
+
event.stopImmediatePropagation();
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
// Ctrl+G: edit the current draft in $VISUAL/$EDITOR (issue #123,
|
|
455
471
|
// readline's edit-and-execute-command). The draft is written to a temp
|
|
456
472
|
// file, the terminal is handed to the editor (Ink's alt-screen handoff),
|
|
457
473
|
// and the saved text replaces the input when it differs. The util maps
|
|
458
474
|
// every failure to an outcome, but the catch/finally here is the hard
|
|
459
475
|
// guarantee: a rejected promise must never kill the process, and the
|
|
460
|
-
// busy flag must always clear or Ctrl+
|
|
461
|
-
if (
|
|
476
|
+
// busy flag must always clear or Ctrl+G stays locked forever.
|
|
477
|
+
if (key.ctrl && input === 'g') {
|
|
462
478
|
editorBusyRef.current = true;
|
|
463
479
|
void (async () => {
|
|
464
480
|
try {
|
|
@@ -521,10 +537,13 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
521
537
|
}
|
|
522
538
|
if (channel.working && value.trim() !== '') {
|
|
523
539
|
// CC's immediate-command semantics: /btw is exempt from steering —
|
|
524
|
-
// the side question never interrupts the running turn.
|
|
525
|
-
//
|
|
540
|
+
// the side question never interrupts the running turn. Hidden
|
|
541
|
+
// UI-only easter eggs (e.g. /deepseek) are also safe to run while
|
|
542
|
+
// streaming. Every other input keeps the steer behavior so /new
|
|
543
|
+
// /model etc. stay idle-only.
|
|
526
544
|
const parsed = value.startsWith('/') ? parseCommandName(value) : undefined;
|
|
527
|
-
if (parsed
|
|
545
|
+
if (parsed !== undefined && ((parsed.name === 'btw' && channel.commandList.some(c => c.name === 'btw'))
|
|
546
|
+
|| isHiddenCommandName(parsed.name))) {
|
|
528
547
|
tryRunCommand(value);
|
|
529
548
|
return;
|
|
530
549
|
}
|
|
@@ -583,6 +602,12 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
583
602
|
handleEnter();
|
|
584
603
|
return;
|
|
585
604
|
}
|
|
605
|
+
// Help is modal over the composer. Backtab must not cycle the session
|
|
606
|
+
// mode invisibly behind it, and plain Tab has no Help action.
|
|
607
|
+
if (helpOpen && key.tab) {
|
|
608
|
+
event.stopImmediatePropagation();
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
586
611
|
// Shift+Tab cycles the configured session modes (default: 默认 →
|
|
587
612
|
// 计划模式 → 完全访问; each mode bundles plan/sandbox/approval atoms —
|
|
588
613
|
// see the `modes` config). Must precede the plain-Tab arms — the parser
|
|
@@ -609,6 +634,41 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
609
634
|
queueSend(value);
|
|
610
635
|
return;
|
|
611
636
|
}
|
|
637
|
+
// Help is a viewport, not prompt history. It deliberately owns every
|
|
638
|
+
// vertical navigation event while visible; otherwise Up/Down silently
|
|
639
|
+
// walk the input history and the clipped command rows remain unreachable.
|
|
640
|
+
if (helpOpen) {
|
|
641
|
+
const page = Math.max(1, helpViewportHeight - 2);
|
|
642
|
+
if (key.upArrow || key.wheelUp) {
|
|
643
|
+
helpScrollRef.current?.scrollBy(key.wheelUp ? -3 : -1);
|
|
644
|
+
event.stopImmediatePropagation();
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
if (key.downArrow || key.wheelDown) {
|
|
648
|
+
helpScrollRef.current?.scrollBy(key.wheelDown ? 3 : 1);
|
|
649
|
+
event.stopImmediatePropagation();
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
if (key.pageUp || key.pageDown) {
|
|
653
|
+
helpScrollRef.current?.scrollBy(key.pageUp ? -page : page);
|
|
654
|
+
event.stopImmediatePropagation();
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
if (key.home) {
|
|
658
|
+
helpScrollRef.current?.scrollTo(0);
|
|
659
|
+
event.stopImmediatePropagation();
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
if (key.end) {
|
|
663
|
+
// Use a deliberately oversized absolute target rather than the
|
|
664
|
+
// sticky-bottom path: compact Help may still be measuring nested
|
|
665
|
+
// sections in this commit, while ScrollBox's render clamp resolves
|
|
666
|
+
// the target to the exact current maximum without a follow-up frame.
|
|
667
|
+
helpScrollRef.current?.scrollTo(Number.MAX_SAFE_INTEGER);
|
|
668
|
+
event.stopImmediatePropagation();
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
612
672
|
if (key.meta && key.upArrow) {
|
|
613
673
|
// Alt+Up: pull the last pending message back for editing (pi/Codex).
|
|
614
674
|
pullBackLast();
|
|
@@ -883,7 +943,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
883
943
|
// 的 style.position,常驻浮层 + 移除普通子节点不会触发 blit 解毒,被
|
|
884
944
|
// 覆盖的转录行会留空(见 Chat.tsx dialogOverlayOpen 注释)。
|
|
885
945
|
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
|
|
946
|
+
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
947
|
.filter(item => item.placement === 'steer')
|
|
888
948
|
.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
949
|
.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"}
|
|
@@ -4,98 +4,15 @@ import { Box, Text } from '../ui.js';
|
|
|
4
4
|
import * as JsDiff from 'diff';
|
|
5
5
|
import { extname } from 'node:path';
|
|
6
6
|
import { getCliHighlightPromise } from '../cc/cliHighlight.js';
|
|
7
|
-
import {
|
|
7
|
+
import { highlightLines, syntaxThemeSignature } from '../cc/syntaxRuns.js';
|
|
8
8
|
// Backward-compatible re-export: repro scripts import chalkFromToken from
|
|
9
9
|
// this module's old home.
|
|
10
10
|
export { chalkFromToken } from '../cc/syntaxTheme.js';
|
|
11
|
+
export { parseAnsiRuns, highlightLines } from '../cc/syntaxRuns.js';
|
|
11
12
|
import { getTheme } from '../theme.js';
|
|
12
13
|
import { useTheme } from './design-system/ThemeProvider.js';
|
|
13
14
|
/** Replace tabs so width math and alignment hold (pi convention). */
|
|
14
15
|
const expandTabs = (text) => text.replaceAll('\t', ' ');
|
|
15
|
-
// --- syntax highlighting ----------------------------------------------------
|
|
16
|
-
// The hljs-class → theme-token map and chalkFromToken live in
|
|
17
|
-
// ../cc/syntaxTheme.ts (shared with the markdown code-block renderer).
|
|
18
|
-
/** ANSI 16-color SGR codes → rgb() strings (the dark-ansi palette path). */
|
|
19
|
-
const ANSI16 = {
|
|
20
|
-
30: 'rgb(0,0,0)', 31: 'rgb(128,0,0)', 32: 'rgb(0,128,0)', 33: 'rgb(128,128,0)',
|
|
21
|
-
34: 'rgb(0,0,128)', 35: 'rgb(128,0,128)', 36: 'rgb(0,128,128)', 37: 'rgb(192,192,192)',
|
|
22
|
-
90: 'rgb(128,128,128)', 91: 'rgb(255,0,0)', 92: 'rgb(0,255,0)', 93: 'rgb(255,255,0)',
|
|
23
|
-
94: 'rgb(0,0,255)', 95: 'rgb(255,0,255)', 96: 'rgb(0,255,255)', 97: 'rgb(255,255,255)',
|
|
24
|
-
};
|
|
25
|
-
/** Parse a cli-highlight ANSI string into colored runs, preserving open
|
|
26
|
-
* spans across embedded newlines (multi-line strings/comments). Handles
|
|
27
|
-
* truecolor 38;2, 256-color 38;5 (tmux / FORCE_COLOR=2, issue #250 P1-1),
|
|
28
|
-
* and single-code 16-color SGR. */
|
|
29
|
-
export function parseAnsiRuns(text) {
|
|
30
|
-
const runs = [];
|
|
31
|
-
let color;
|
|
32
|
-
let rest = text;
|
|
33
|
-
// eslint-disable-next-line no-control-regex -- parsing SGR is the point
|
|
34
|
-
const sgr = /\x1b\[([0-9;]+)m/;
|
|
35
|
-
while (rest.length > 0) {
|
|
36
|
-
const match = sgr.exec(rest);
|
|
37
|
-
const chunk = match === null ? rest : rest.slice(0, match.index);
|
|
38
|
-
if (chunk !== '')
|
|
39
|
-
runs.push(color === undefined ? { text: chunk } : { text: chunk, color });
|
|
40
|
-
if (match === null)
|
|
41
|
-
break;
|
|
42
|
-
const codes = match[1].split(';').map(Number);
|
|
43
|
-
if (codes.includes(0) || codes.includes(39))
|
|
44
|
-
color = undefined;
|
|
45
|
-
else if (codes[0] === 38 && codes[1] === 2 && codes.length >= 5) {
|
|
46
|
-
color = `rgb(${codes[2]},${codes[3]},${codes[4]})`;
|
|
47
|
-
}
|
|
48
|
-
else if (codes[0] === 38 && codes[1] === 5 && codes.length >= 3) {
|
|
49
|
-
color = `ansi256(${codes[2]})`;
|
|
50
|
-
}
|
|
51
|
-
else if (codes.length === 1 && ANSI16[codes[0]] !== undefined) {
|
|
52
|
-
color = ANSI16[codes[0]];
|
|
53
|
-
}
|
|
54
|
-
rest = rest.slice(match.index + match[0].length);
|
|
55
|
-
}
|
|
56
|
-
return runs;
|
|
57
|
-
}
|
|
58
|
-
/** Split flat runs into per-line run arrays at embedded newlines. */
|
|
59
|
-
function splitRunsToLines(runs) {
|
|
60
|
-
const lines = [[]];
|
|
61
|
-
for (const run of runs) {
|
|
62
|
-
const parts = run.text.split('\n');
|
|
63
|
-
for (let i = 0; i < parts.length; i++) {
|
|
64
|
-
if (i > 0)
|
|
65
|
-
lines.push([]);
|
|
66
|
-
if (parts[i] !== '') {
|
|
67
|
-
lines[lines.length - 1].push(run.color === undefined ? { text: parts[i] } : { text: parts[i], color: run.color });
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return lines;
|
|
72
|
-
}
|
|
73
|
-
/** Whole-hunk syntax runs per line, cached per (theme signature + language
|
|
74
|
-
* + text): one entry survives any amount of card re-rendering, and the
|
|
75
|
-
* palette change invalidates it (issue #250, P1-2 + P2-8). */
|
|
76
|
-
const syntaxCache = new Map();
|
|
77
|
-
const SYNTAX_CACHE_MAX = 40;
|
|
78
|
-
export function highlightLines(text, language, hl, chTheme, themeSig) {
|
|
79
|
-
if (hl === null || chTheme === undefined || language === undefined)
|
|
80
|
-
return undefined;
|
|
81
|
-
if (text === '' || !hl.supportsLanguage(language))
|
|
82
|
-
return undefined;
|
|
83
|
-
const key = `${themeSig}${language}${text}`;
|
|
84
|
-
const cached = syntaxCache.get(key);
|
|
85
|
-
if (cached !== undefined)
|
|
86
|
-
return cached;
|
|
87
|
-
let lines;
|
|
88
|
-
try {
|
|
89
|
-
lines = splitRunsToLines(parseAnsiRuns(hl.highlight(text, { language, theme: chTheme })));
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
return undefined;
|
|
93
|
-
}
|
|
94
|
-
if (syntaxCache.size >= SYNTAX_CACHE_MAX)
|
|
95
|
-
syntaxCache.clear();
|
|
96
|
-
syntaxCache.set(key, lines);
|
|
97
|
-
return lines;
|
|
98
|
-
}
|
|
99
16
|
/**
|
|
100
17
|
* Merge syntax runs with word-diff change flags over the same string: both
|
|
101
18
|
* partition it, so an offset sweep yields runs carrying a syntax color AND
|
|
@@ -306,24 +223,8 @@ export function SplitDiffView({ diffs, width, maxRows, verbose, toolBackground =
|
|
|
306
223
|
const [themeName] = useTheme();
|
|
307
224
|
// Resolved-palette signature: covers dark→light, light→dark, AND the
|
|
308
225
|
// `auto` base flip where the name never changes (issue #250, P1-2).
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
return Object.values(SYNTAX_CLASS_TO_TOKEN)
|
|
312
|
-
.map(key => theme[key] ?? '')
|
|
313
|
-
.join('|');
|
|
314
|
-
}, [themeName]);
|
|
315
|
-
const chTheme = React.useMemo(() => {
|
|
316
|
-
const theme = getTheme(themeName);
|
|
317
|
-
const out = {};
|
|
318
|
-
for (const [tokenClass, tokenKey] of Object.entries(SYNTAX_CLASS_TO_TOKEN)) {
|
|
319
|
-
const value = theme[tokenKey];
|
|
320
|
-
if (value !== undefined)
|
|
321
|
-
out[tokenClass] = chalkFromToken(value);
|
|
322
|
-
}
|
|
323
|
-
return out;
|
|
324
|
-
// themeSig changes exactly when any syntax token's resolved value does.
|
|
325
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
326
|
-
}, [themeSig]);
|
|
226
|
+
const syntaxTheme = React.useMemo(() => getTheme(themeName), [themeName]);
|
|
227
|
+
const themeSig = React.useMemo(() => syntaxThemeSignature(syntaxTheme), [syntaxTheme]);
|
|
327
228
|
// Alignment is text-only and capped BEFORE styling: the highlighter and
|
|
328
229
|
// word-diff work lands only on the visible slice (issue #250, P2-8).
|
|
329
230
|
const rows = [];
|
|
@@ -348,8 +249,8 @@ export function SplitDiffView({ diffs, width, maxRows, verbose, toolBackground =
|
|
|
348
249
|
const fileSyntax = diffs.map(diff => {
|
|
349
250
|
const language = extname(diff.path).replace(/^\./, '') || undefined;
|
|
350
251
|
return {
|
|
351
|
-
old: highlightLines(expandTabs(diff.oldText ?? ''), language, hl,
|
|
352
|
-
next: highlightLines(expandTabs(diff.newText), language, hl,
|
|
252
|
+
old: highlightLines(expandTabs(diff.oldText ?? ''), language, hl, syntaxTheme, themeSig),
|
|
253
|
+
next: highlightLines(expandTabs(diff.newText), language, hl, syntaxTheme, themeSig),
|
|
353
254
|
};
|
|
354
255
|
});
|
|
355
256
|
return (_jsxs(Box, { flexDirection: "column", width: paneWidth * 2 + 1, children: [visible.map((row, index) => {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { SubagentState } from '../dsh-adapter/subagents.js';
|
|
3
|
+
export interface SubagentCardProps {
|
|
4
|
+
subagent: SubagentState;
|
|
5
|
+
focused?: boolean;
|
|
6
|
+
onClick?: () => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function SubagentCard({ subagent, focused, onClick }: SubagentCardProps): React.ReactNode;
|
|
9
|
+
//# sourceMappingURL=SubagentCard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SubagentCard.d.ts","sourceRoot":"","sources":["../../../src/components/SubagentCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAShE,MAAM,WAAW,iBAAiB;IAAG,QAAQ,EAAE,aAAa,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE;AAEvG,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,iBAAiB,GAAG,KAAK,CAAC,SAAS,CA4B/F"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from '../ui.js';
|
|
3
|
+
import { t } from '../i18n.js';
|
|
4
|
+
import { isMinimalMode } from '../minimalMode.js';
|
|
5
|
+
function formatDuration(ms) {
|
|
6
|
+
const seconds = Math.floor(ms / 1000);
|
|
7
|
+
return seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}m${seconds % 60}s`;
|
|
8
|
+
}
|
|
9
|
+
export function SubagentCard({ subagent, focused, onClick }) {
|
|
10
|
+
const running = subagent.status === 'running' || subagent.status === 'starting';
|
|
11
|
+
const elapsed = subagent.completedAt ? subagent.completedAt - subagent.startedAt : Date.now() - subagent.startedAt;
|
|
12
|
+
const total = subagent.tokens?.total ?? ((subagent.tokens?.input ?? 0) + (subagent.tokens?.output ?? 0) || 0);
|
|
13
|
+
// Live preview: the newest streamed line rides under the header while the
|
|
14
|
+
// subagent runs, then folds away — the dashboard stays one line per settled
|
|
15
|
+
// subagent.
|
|
16
|
+
const liveLine = running ? subagent.output[subagent.output.length - 1] : undefined;
|
|
17
|
+
const minimal = isMinimalMode();
|
|
18
|
+
const glyph = running ? (minimal ? '·' : '🟡')
|
|
19
|
+
: subagent.status === 'failed' || subagent.status === 'cancelled' ? (minimal ? '×' : '🔴')
|
|
20
|
+
: (minimal ? '✓' : '🟢');
|
|
21
|
+
const glyphColor = minimal ? undefined
|
|
22
|
+
: running ? 'warning'
|
|
23
|
+
: subagent.status === 'failed' || subagent.status === 'cancelled' ? 'error'
|
|
24
|
+
: 'success';
|
|
25
|
+
return _jsxs(Box, { flexDirection: "column", paddingLeft: 1, marginBottom: 1, onClick: onClick, children: [_jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Text, { color: glyphColor, children: glyph }), _jsx(Text, { bold: true, color: focused ? 'claude' : undefined, children: `${t('subagent-card-prefix')}${subagent.description}` }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: ' · ' }), _jsx(Text, { children: subagent.model ?? subagent.provider ?? 'default' }), _jsx(Text, { dimColor: true, children: ` · ${formatDuration(elapsed)} · ${total || '—'} tok · ${subagent.toolCalls.length} tools` })] })] }), liveLine && _jsx(Text, { dimColor: true, wrap: "truncate", children: ` │ ${liveLine}` })] });
|
|
26
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { SubagentState } from '../dsh-adapter/subagents.js';
|
|
3
|
+
export interface SubagentDashboardProps {
|
|
4
|
+
subagents: SubagentState[];
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
onSelect?: (agentId: string) => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* SubagentDashboard — overlay panel showing all active/recent subagents.
|
|
10
|
+
* Keyboard: up/down to navigate, Enter to view detail, Esc to close.
|
|
11
|
+
*/
|
|
12
|
+
export declare function SubagentDashboard({ subagents, onClose, onSelect }: SubagentDashboardProps): React.ReactNode;
|
|
13
|
+
//# sourceMappingURL=SubagentDashboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SubagentDashboard.d.ts","sourceRoot":"","sources":["../../../src/components/SubagentDashboard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAMhE,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,aAAa,EAAE,CAAA;IAC1B,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CACrC;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,OAAO,EACP,QAAQ,EACT,EAAE,sBAAsB,GAAG,KAAK,CAAC,SAAS,CAoG1C"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Box, Text, useInput, ScrollBox, useTerminalSize } from '../ui.js';
|
|
4
|
+
import { SubagentCard } from './SubagentCard.js';
|
|
5
|
+
import { t } from '../i18n.js';
|
|
6
|
+
import { Divider } from './design-system/Divider.js';
|
|
7
|
+
import { isPlainReturnInput } from '../utils/modifiers.js';
|
|
8
|
+
/**
|
|
9
|
+
* SubagentDashboard — overlay panel showing all active/recent subagents.
|
|
10
|
+
* Keyboard: up/down to navigate, Enter to view detail, Esc to close.
|
|
11
|
+
*/
|
|
12
|
+
export function SubagentDashboard({ subagents, onClose, onSelect }) {
|
|
13
|
+
const [focusIndex, setFocusIndex] = React.useState(0);
|
|
14
|
+
const scrollRef = React.useRef(null);
|
|
15
|
+
const { rows, columns } = useTerminalSize();
|
|
16
|
+
useInput((input, key, event) => {
|
|
17
|
+
if (key.escape || (key.ctrl && input === 'c')) {
|
|
18
|
+
event.stopImmediatePropagation();
|
|
19
|
+
onClose();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (key.upArrow) {
|
|
23
|
+
event.stopImmediatePropagation();
|
|
24
|
+
setFocusIndex(i => Math.max(0, i - 1));
|
|
25
|
+
scrollRef.current?.scrollBy(-3);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (key.downArrow) {
|
|
29
|
+
event.stopImmediatePropagation();
|
|
30
|
+
setFocusIndex(i => Math.min(subagents.length - 1, i + 1));
|
|
31
|
+
scrollRef.current?.scrollBy(3);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (isPlainReturnInput(input, key) && onSelect) {
|
|
35
|
+
event.stopImmediatePropagation();
|
|
36
|
+
const selected = subagents[focusIndex];
|
|
37
|
+
if (selected)
|
|
38
|
+
onSelect(selected.agentId);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// Consume all input while dashboard is open
|
|
42
|
+
event.stopImmediatePropagation();
|
|
43
|
+
});
|
|
44
|
+
const running = subagents.filter(s => s.status === 'running').length;
|
|
45
|
+
const completed = subagents.filter(s => s.status === 'completed').length;
|
|
46
|
+
const failed = subagents.filter(s => s.status === 'failed').length;
|
|
47
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [_jsx(Divider, { color: "claude", title: t('subagent-dashboard-title'), padding: 4 }), _jsxs(Box, { flexDirection: "row", gap: 3, marginTop: 1, marginBottom: 1, children: [_jsxs(Text, { children: [_jsx(Text, { color: "claude", children: running }), _jsxs(Text, { dimColor: true, children: [" ", t('subagent-count-running')] })] }), _jsxs(Text, { children: [_jsx(Text, { color: "success", children: completed }), _jsxs(Text, { dimColor: true, children: [" ", t('subagent-count-completed')] })] }), failed > 0 && (_jsxs(Text, { children: [_jsx(Text, { color: "error", children: failed }), _jsxs(Text, { dimColor: true, children: [" ", t('subagent-count-failed')] })] }))] }), _jsx(Box, { flexDirection: "column", maxHeight: Math.max(10, rows - 10), marginTop: 1, children: _jsx(ScrollBox, { ref: scrollRef, flexDirection: "column", flexGrow: 1, children: subagents.length === 0 ? (_jsxs(Box, { flexDirection: "column", alignItems: "center", marginTop: Math.max(2, Math.floor((rows - 16) / 3)), children: [_jsx(Text, { dimColor: true, children: '○' }), _jsx(Text, { dimColor: true, children: t('subagent-none') }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: t('subagent-empty-hint') }) })] })) : (subagents.map((subagent, index) => (_jsxs(Box, { flexDirection: "column", children: [_jsx(SubagentCard, { subagent: subagent, focused: index === focusIndex }), index < subagents.length - 1 && (_jsx(Text, { dimColor: true, children: '─'.repeat(Math.max(20, Math.min(72, columns - 6))) }))] }, subagent.agentId)))) }) }), _jsx(Divider, { color: "subtle", title: "", padding: 4 }), _jsx(Box, { marginTop: 0, children: _jsx(Text, { dimColor: true, children: onSelect
|
|
48
|
+
? t('subagent-dashboard-hint-detail')
|
|
49
|
+
: t('subagent-dashboard-hint-basic') }) })] }));
|
|
50
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { SubagentState } from '../dsh-adapter/subagents.js';
|
|
3
|
+
export interface SubagentDetailSceneProps {
|
|
4
|
+
subagent: SubagentState;
|
|
5
|
+
onBack: () => void;
|
|
6
|
+
onInterrupt?: (agentId: string) => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* SubagentDetailScene — full-screen paged detail view for one subagent.
|
|
10
|
+
* Header block (identity + stats) stays fixed; the body pages through
|
|
11
|
+
* 摘要 / 输出 / 工具 with ←/→. Follow-up delivery was removed: the official
|
|
12
|
+
* seam only accepts continuable children, and one-shot spawn children are
|
|
13
|
+
* disposed at settlement, so the affordance would be a dead control.
|
|
14
|
+
*/
|
|
15
|
+
export declare function SubagentDetailScene({ subagent, onBack, onInterrupt, }: SubagentDetailSceneProps): React.ReactNode;
|
|
16
|
+
//# sourceMappingURL=SubagentDetailScene.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SubagentDetailScene.d.ts","sourceRoot":"","sources":["../../../src/components/SubagentDetailScene.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAsGhE,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,aAAa,CAAA;IACvB,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CACxC;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,QAAQ,EACR,MAAM,EACN,WAAW,GACZ,EAAE,wBAAwB,GAAG,KAAK,CAAC,SAAS,CA+L5C"}
|