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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +13 -3
  2. package/bin/dsh-tui.js +269 -218
  3. package/cordis.patch.yml +48 -22
  4. package/lib/types/cc/figures.d.ts.map +1 -1
  5. package/lib/types/cc/figures.js +14 -13
  6. package/lib/types/cc/syntaxRuns.d.ts +16 -0
  7. package/lib/types/cc/syntaxRuns.d.ts.map +1 -0
  8. package/lib/types/cc/syntaxRuns.js +79 -0
  9. package/lib/types/commands.d.ts +14 -0
  10. package/lib/types/commands.d.ts.map +1 -1
  11. package/lib/types/commands.js +20 -1
  12. package/lib/types/components/Chat/SubagentMessage.d.ts +19 -0
  13. package/lib/types/components/Chat/SubagentMessage.d.ts.map +1 -0
  14. package/lib/types/components/Chat/SubagentMessage.js +75 -0
  15. package/lib/types/components/EffortInputBorder.d.ts.map +1 -1
  16. package/lib/types/components/EffortInputBorder.js +2 -2
  17. package/lib/types/components/EffortTierBadge.js +1 -1
  18. package/lib/types/components/HelpMenu.d.ts +8 -1
  19. package/lib/types/components/HelpMenu.d.ts.map +1 -1
  20. package/lib/types/components/HelpMenu.js +15 -4
  21. package/lib/types/components/LogoV2.d.ts +3 -1
  22. package/lib/types/components/LogoV2.d.ts.map +1 -1
  23. package/lib/types/components/LogoV2.js +2 -2
  24. package/lib/types/components/MessageList.d.ts +6 -2
  25. package/lib/types/components/MessageList.d.ts.map +1 -1
  26. package/lib/types/components/MessageList.js +17 -6
  27. package/lib/types/components/ModelPicker.js +2 -2
  28. package/lib/types/components/PromptInput.d.ts +1 -1
  29. package/lib/types/components/PromptInput.d.ts.map +1 -1
  30. package/lib/types/components/PromptInput.js +75 -15
  31. package/lib/types/components/SkillsPicker.js +2 -2
  32. package/lib/types/components/SplitDiffView.d.ts +1 -13
  33. package/lib/types/components/SplitDiffView.d.ts.map +1 -1
  34. package/lib/types/components/SplitDiffView.js +6 -105
  35. package/lib/types/components/SubagentCard.d.ts +9 -0
  36. package/lib/types/components/SubagentCard.d.ts.map +1 -0
  37. package/lib/types/components/SubagentCard.js +26 -0
  38. package/lib/types/components/SubagentDashboard.d.ts +13 -0
  39. package/lib/types/components/SubagentDashboard.d.ts.map +1 -0
  40. package/lib/types/components/SubagentDashboard.js +50 -0
  41. package/lib/types/components/SubagentDetailScene.d.ts +16 -0
  42. package/lib/types/components/SubagentDetailScene.d.ts.map +1 -0
  43. package/lib/types/components/SubagentDetailScene.js +141 -0
  44. package/lib/types/components/SyntaxText.d.ts +15 -0
  45. package/lib/types/components/SyntaxText.d.ts.map +1 -0
  46. package/lib/types/components/SyntaxText.js +34 -0
  47. package/lib/types/components/ThinkingToggle.d.ts +3 -10
  48. package/lib/types/components/ThinkingToggle.d.ts.map +1 -1
  49. package/lib/types/components/ThinkingToggle.js +4 -9
  50. package/lib/types/components/design-system/ListItem.d.ts.map +1 -1
  51. package/lib/types/components/design-system/ListItem.js +1 -1
  52. package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
  53. package/lib/types/components/messages/AssistantThinkingMessage.js +18 -7
  54. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +2 -0
  55. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  56. package/lib/types/components/messages/AssistantToolUseMessage.js +42 -6
  57. package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
  58. package/lib/types/components/messages/UserPromptMessage.js +15 -2
  59. package/lib/types/dsh-adapter/channel.d.ts +53 -2
  60. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  61. package/lib/types/dsh-adapter/channel.js +294 -7
  62. package/lib/types/dsh-adapter/contract.d.ts +27 -4
  63. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  64. package/lib/types/dsh-adapter/contract.js +56 -10
  65. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  66. package/lib/types/dsh-adapter/index.js +9 -3
  67. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  68. package/lib/types/dsh-adapter/plugin.js +38 -0
  69. package/lib/types/dsh-adapter/shortcuts.js +3 -3
  70. package/lib/types/dsh-adapter/subagents.d.ts +87 -0
  71. package/lib/types/dsh-adapter/subagents.d.ts.map +1 -0
  72. package/lib/types/dsh-adapter/subagents.js +204 -0
  73. package/lib/types/herdr.d.ts +28 -0
  74. package/lib/types/herdr.d.ts.map +1 -0
  75. package/lib/types/herdr.js +133 -0
  76. package/lib/types/i18n.d.ts +215 -31
  77. package/lib/types/i18n.d.ts.map +1 -1
  78. package/lib/types/i18n.js +65 -14
  79. package/lib/types/ink/ink.d.ts +4 -0
  80. package/lib/types/ink/ink.d.ts.map +1 -1
  81. package/lib/types/ink/ink.js +46 -15
  82. package/lib/types/minimalMode.d.ts +13 -0
  83. package/lib/types/minimalMode.d.ts.map +1 -0
  84. package/lib/types/minimalMode.js +17 -0
  85. package/lib/types/screens/Chat.d.ts +1 -1
  86. package/lib/types/screens/Chat.d.ts.map +1 -1
  87. package/lib/types/screens/Chat.js +94 -43
  88. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  89. package/lib/types/screens/StatusLine.js +6 -2
  90. package/lib/types/screens/TrajectoryScene.d.ts.map +1 -1
  91. package/lib/types/screens/TrajectoryScene.js +4 -2
  92. package/lib/types/theme.d.ts +8 -0
  93. package/lib/types/theme.d.ts.map +1 -1
  94. package/lib/types/theme.js +38 -14
  95. package/lib/types/tips.js +3 -3
  96. package/lib/types/update.d.ts +18 -0
  97. package/lib/types/update.d.ts.map +1 -1
  98. package/lib/types/update.js +73 -2
  99. package/lib/types/utils/execFileNoThrow.d.ts +3 -20
  100. package/lib/types/utils/execFileNoThrow.d.ts.map +1 -1
  101. package/lib/types/utils/execFileNoThrow.js +53 -32
  102. package/lib/types/utils/externalEditor.d.ts +12 -11
  103. package/lib/types/utils/externalEditor.d.ts.map +1 -1
  104. package/lib/types/utils/externalEditor.js +11 -11
  105. package/package.json +64 -58
@@ -1 +1 @@
1
- {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/MessageList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAA8B,KAAK,eAAe,EAAE,MAAM,UAAU,CAAA;AAC3E,OAAO,KAAK,EAAE,OAAO,EAAyC,MAAM,2BAA2B,CAAA;AAC/F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAa/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AA2B3D,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,WAAW,EACX,KAAK,EACL,UAAmB,EACnB,YAAwB,EACxB,cAAuB,EACvB,OAAO,EACP,WAAW,EACX,WAAW,EACX,eAAsB,EACtB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,WAAW,GACZ,EAAE;IACD,IAAI,EAAE,SAAS,OAAO,EAAE,CAAA;IACxB,QAAQ,EAAE,OAAO,CAAA;IACjB,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACjC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACpC,KAAK,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;IACzC,mEAAmE;IACnE,YAAY,CAAC,EAAE,SAAS,GAAG,MAAM,CAAA;IACjC,qEAAqE;IACrE,cAAc,CAAC,EAAE,cAAc,CAAA;IAC/B,OAAO,EAAE,OAAO,CAAA;IAChB,WAAW,EAAE,MAAM,IAAI,CAAA;IACvB;2EACuE;IACvE,WAAW,CAAC,EAAE,MAAM,IAAI,CAAA;IACxB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,GAAG,IAAI,KAAK,IAAI,CAAA;IAC/D,oDAAoD;IACpD,YAAY,CAAC,EAAE,eAAe,GAAG,IAAI,CAAA;IACrC,4EAA4E;IAC5E,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B;8EAC0E;IAC1E,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,0EAA0E;IAC1E,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,qBA8VA;AAsRD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,MAAM,EACN,GAAG,GACJ,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,GAAG,EAAE,MAAM,CAAA;CACZ,GAAG,KAAK,CAAC,SAAS,CAMlB"}
1
+ {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/MessageList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAA8B,KAAK,eAAe,EAAE,MAAM,UAAU,CAAA;AAC3E,OAAO,KAAK,EAAE,OAAO,EAAsD,MAAM,2BAA2B,CAAA;AAC5G,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAe/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AA2B3D,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,WAAW,EACX,KAAK,EACL,UAAmB,EACnB,YAAwB,EACxB,cAAuB,EACvB,cAAc,EACd,OAAO,EACP,WAAW,EACX,WAAW,EACX,eAAsB,EACtB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,WAAW,GACZ,EAAE;IACD,IAAI,EAAE,SAAS,OAAO,EAAE,CAAA;IACxB,QAAQ,EAAE,OAAO,CAAA;IACjB,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACjC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACpC,KAAK,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;IACzC,mEAAmE;IACnE,YAAY,CAAC,EAAE,SAAS,GAAG,MAAM,CAAA;IACjC,qEAAqE;IACrE,cAAc,CAAC,EAAE,cAAc,CAAA;IAC/B;sEACkE;IAClE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,OAAO,EAAE,OAAO,CAAA;IAChB,WAAW,EAAE,MAAM,IAAI,CAAA;IACvB;2EACuE;IACvE,WAAW,CAAC,EAAE,MAAM,IAAI,CAAA;IACxB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,GAAG,IAAI,KAAK,IAAI,CAAA;IAC/D,oDAAoD;IACpD,YAAY,CAAC,EAAE,eAAe,GAAG,IAAI,CAAA;IACrC,4EAA4E;IAC5E,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B;8EAC0E;IAC1E,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,0EAA0E;IAC1E,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,qBAiWA;AA2SD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,MAAM,EACN,GAAG,EACH,KAAY,GACb,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB,GAAG,KAAK,CAAC,SAAS,CASlB"}
@@ -7,6 +7,8 @@ import { UserPromptMessage } from './messages/UserPromptMessage.js';
7
7
  import { AssistantTextMessage } from './messages/AssistantTextMessage.js';
8
8
  import { AssistantThinkingMessage } from './messages/AssistantThinkingMessage.js';
9
9
  import { AssistantToolUseMessage } from './messages/AssistantToolUseMessage.js';
10
+ import { SubagentMessage } from './Chat/SubagentMessage.js';
11
+ import { isMinimalMode } from '../minimalMode.js';
10
12
  import { InterruptedByUser } from './InterruptedByUser.js';
11
13
  import { LogoV2 } from './LogoV2.js';
12
14
  import { StreamingMarkdown } from './StreamingMarkdown.js';
@@ -37,7 +39,7 @@ const DEFAULT_ROW_HEIGHT = 2;
37
39
  /** Cold-start estimate of the header block above the rows; corrected by the
38
40
  * first layout measurement. */
39
41
  const DEFAULT_HEADER_LINES = 14;
40
- export function MessageList({ rows, expanded, expandedRows, selectedId, onToggleRow, model, diffLayout = 'auto', thinkingFold = 'preview', toolBackground = 'none', showAll, onToggleAll, onLoadOlder, thinkingVisible = true, registerRowRef, scrollHandle, forceMountRowId, newSinceRowId, onUnseenCount, failureHintRowId, failureHint, }) {
42
+ export function MessageList({ rows, expanded, expandedRows, selectedId, onToggleRow, model, diffLayout = 'auto', thinkingFold = 'preview', toolBackground = 'none', activityFrames, showAll, onToggleAll, onLoadOlder, thinkingVisible = true, registerRowRef, scrollHandle, forceMountRowId, newSinceRowId, onUnseenCount, failureHintRowId, failureHint, }) {
41
43
  const hiddenCount = rows.length - MAX_RENDERED_ROWS;
42
44
  // The thinking filter runs BEFORE virtualization so window indices line up.
43
45
  const visibleRows = (showAll || hiddenCount <= 0
@@ -332,10 +334,11 @@ export function MessageList({ rows, expanded, expandedRows, selectedId, onToggle
332
334
  // spacing; only the very first row of the whole list has none.
333
335
  const addMargin = margins.get(row.id) === true;
334
336
  const tool = row.tool;
335
- return (_jsx(MemoRow, { rowId: row.id, kind: row.kind, text: row.text, executionTarget: row.executionTarget, streaming: row.streaming === true, durationMs: row.durationMs, time: row.time, addMargin: addMargin, isSelected: selectedId === row.id, isExpanded: expandedRows.has(row.id), expanded: expanded, model: model, diffLayout: diffLayout, thinkingFold: thinkingFold, toolBackground: toolBackground, background: rowBackground(row.id), toolCallId: tool?.callId, toolName: tool?.name, toolArgsText: tool?.argsText, toolArgsFull: tool?.argsFull, toolStatus: tool?.status, toolResultText: tool?.resultText, toolResultFull: tool?.resultFull, toolErrorText: tool?.errorText, toolFootnote: failureHintRowId === row.id ? failureHint : undefined, toolCallView: tool?.callView, toolResultView: tool?.resultView, toolStartedAt: tool?.startedAt, toolDurationMs: tool?.durationMs, nowSec: tool?.status === 'running' ? nowSec : undefined, onToggleRow: onToggleRow, setRowRef: setRowRef }, row.id));
337
+ const subagent = row.kind === 'subagent' ? row.subagent : undefined;
338
+ return (_jsx(MemoRow, { rowId: row.id, kind: row.kind, text: row.text, executionTarget: row.executionTarget, streaming: row.streaming === true, durationMs: row.durationMs, time: row.time, addMargin: addMargin, isSelected: selectedId === row.id, isExpanded: expandedRows.has(row.id), expanded: expanded, model: model, diffLayout: diffLayout, thinkingFold: thinkingFold, toolBackground: toolBackground, activityFrames: activityFrames, background: rowBackground(row.id), toolCallId: tool?.callId, toolName: tool?.name, toolArgsText: tool?.argsText, toolArgsFull: tool?.argsFull, toolStatus: tool?.status, toolResultText: tool?.resultText, toolResultFull: tool?.resultFull, toolErrorText: tool?.errorText, toolFootnote: failureHintRowId === row.id ? failureHint : undefined, toolCallView: tool?.callView, toolResultView: tool?.resultView, toolStartedAt: tool?.startedAt, toolDurationMs: tool?.durationMs, nowSec: tool?.status === 'running' ? nowSec : undefined, subagent: subagent, onToggleRow: onToggleRow, setRowRef: setRowRef }, row.id));
336
339
  }), bottomPad > 0 && _jsx(Box, { height: bottomPad, flexShrink: 0 })] }));
337
340
  }
338
- function TranscriptRow({ rowId, kind, text, executionTarget, streaming, durationMs, time, addMargin, isSelected, isExpanded, expanded, model, diffLayout, thinkingFold, toolBackground, background, toolCallId, toolName, toolArgsText, toolArgsFull, toolStatus, toolResultText, toolResultFull, toolErrorText, toolFootnote, toolCallView, toolResultView, toolStartedAt, toolDurationMs, onToggleRow, setRowRef, }) {
341
+ function TranscriptRow({ rowId, kind, text, executionTarget, streaming, durationMs, time, addMargin, isSelected, isExpanded, expanded, model, diffLayout, thinkingFold, toolBackground, activityFrames, background, toolCallId, toolName, toolArgsText, toolArgsFull, toolStatus, toolResultText, toolResultFull, toolErrorText, toolFootnote, toolCallView, toolResultView, toolStartedAt, toolDurationMs, subagent, onToggleRow, setRowRef, }) {
339
342
  const ref = React.useCallback((el) => {
340
343
  setRowRef(rowId, el);
341
344
  }, [setRowRef, rowId]);
@@ -346,7 +349,7 @@ function TranscriptRow({ rowId, kind, text, executionTarget, streaming, duration
346
349
  case 'user':
347
350
  return (_jsx(Box, { flexDirection: "column", ref: ref, children: _jsx(UserPromptMessage, { text: text, addMargin: addMargin, isSelected: isSelected, onClick: onClick }) }));
348
351
  case 'assistant':
349
- return streaming ? (_jsxs(Box, { alignItems: "flex-start", flexDirection: "row", marginTop: addMargin ? 1 : 0, width: "100%", backgroundColor: background, children: [_jsx(Box, { minWidth: 2, children: _jsx(Text, { color: "text", children: "\u25CF" }) }), _jsx(Box, { flexDirection: "column", children: _jsx(StreamingMarkdown, { children: stripNarration(text) }) })] })) : (_jsxs(Box, { width: "100%", flexDirection: "column", backgroundColor: background, ref: ref, children: [expanded && (_jsx(Box, { flexDirection: "row", justifyContent: "flex-end", gap: 1, marginTop: 1, children: _jsx(MessageMetadata, { timestamp: time, model: model }) })), _jsx(AssistantTextMessage, { text: stripNarration(text), addMargin: addMargin, isSelected: isSelected, isExpanded: isExpanded, onClick: onClick })] }));
352
+ return streaming ? (_jsxs(Box, { alignItems: "flex-start", flexDirection: "row", marginTop: addMargin ? 1 : 0, width: "100%", backgroundColor: background, ref: ref, children: [_jsx(Box, { minWidth: 2, children: _jsx(Text, { color: "text", children: "\u25CF" }) }), _jsx(Box, { flexDirection: "column", children: _jsx(StreamingMarkdown, { children: stripNarration(text) }) })] })) : (_jsxs(Box, { width: "100%", flexDirection: "column", backgroundColor: background, ref: ref, children: [expanded && (_jsx(Box, { flexDirection: "row", justifyContent: "flex-end", gap: 1, marginTop: 1, children: _jsx(MessageMetadata, { timestamp: time, model: model }) })), _jsx(AssistantTextMessage, { text: stripNarration(text), addMargin: addMargin, isSelected: isSelected, isExpanded: isExpanded, onClick: onClick })] }));
350
353
  case 'reasoning':
351
354
  return (_jsx(Box, { flexDirection: "column", ref: ref, children: _jsx(AssistantThinkingMessage, { thinking: text, addMargin: addMargin, streaming: streaming, preview: streaming &&
352
355
  thinkingFold === 'preview' &&
@@ -396,6 +399,10 @@ function TranscriptRow({ rowId, kind, text, executionTarget, streaming, duration
396
399
  // text preview; Ctrl+O (global) or message-selection Enter reveals
397
400
  // the full summary.
398
401
  return (_jsx(Box, { marginTop: addMargin ? 1 : 0, paddingLeft: 2, backgroundColor: background, ref: ref, onClick: onClick, children: expanded || isExpanded ? (_jsx(Text, { dimColor: true, children: text })) : (_jsxs(Text, { dimColor: true, italic: true, children: ["\u2234 ", t('compact-summary-folded'), " \u00B7 ", compactPreview(text), ' ', t('hint-expand-ctrl-o')] })) }));
402
+ case 'subagent':
403
+ if (!subagent)
404
+ return null;
405
+ return (_jsx(Box, { flexDirection: "column", ref: ref, children: _jsx(SubagentMessage, { subagent: subagent, addMargin: addMargin, activityFrames: activityFrames, isExpanded: isExpanded, onClick: () => onToggleRow(rowId) }) }));
399
406
  }
400
407
  }
401
408
  /** Folded compact-summary preview: whitespace flattened, capped with an
@@ -412,6 +419,10 @@ const MemoRow = React.memo(TranscriptRow);
412
419
  * welcome line. It scrolls away with the transcript once the conversation
413
420
  * fills the viewport (Claude Code shows its ✦ logo in the same slot).
414
421
  */
415
- export function LogoHeader({ model, effort, cwd, }) {
416
- return (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: _jsx(LogoV2, { model: model, effort: effort, cwd: cwd }) }));
422
+ export function LogoHeader({ model, effort, cwd, whale = true, }) {
423
+ // Minimal mode drops the whole splash (whale art AND wordmark) — only the
424
+ // transcript and a bare status bar remain.
425
+ if (isMinimalMode())
426
+ return null;
427
+ return (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: _jsx(LogoV2, { model: model, effort: effort, cwd: cwd, whale: whale }) }));
417
428
  }
@@ -18,8 +18,8 @@ export function ModelPicker({ models, focusIndex, currentModel, }) {
18
18
  const { rows: terminalRows } = useTerminalSize();
19
19
  // 焦点窗口化按行预算:ListItem 带 description 时占 2 行(正文+描述,均
20
20
  // truncate 成单行),只数项数会把焦点裁出浮层(二次审查实证)。
21
- // 框架行:浮层预留 8 + Pane 2 + 标题 2 + 页脚 1 = 13。
22
- const { start, end } = listWindow(models.map(m => (m.description ? 2 : 1)), focusIndex, Math.max(terminalRows - 13, 2));
21
+ // 框架行:浮层预留 8 + Pane 2 + 标题 2 + 页脚 1 + 挂载包裹 marginTop 1 = 14。
22
+ const { start, end } = listWindow(models.map(m => (m.description ? 2 : 1)), focusIndex, Math.max(terminalRows - 14, 2));
23
23
  return (_jsxs(Pane, { color: "permission", children: [_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "remember", bold: true, children: t('picker-title-model') }) }), models.slice(start, end).map((model, index) => {
24
24
  const absoluteIndex = start + index;
25
25
  return (_jsxs(ListItem, { isFocused: absoluteIndex === focusIndex, isSelected: `${model.provider}/${model.id}` === currentModel, description: model.description, showScrollUp: absoluteIndex === start && start > 0, showScrollDown: absoluteIndex === end - 1 && end < models.length, children: [model.provider, " / ", model.name] }, `${model.provider}/${model.id}`));
@@ -49,7 +49,7 @@ export interface PromptInputProps {
49
49
  * the input spans multiple lines (history/command selection otherwise); the
50
50
  * visible window scrolls to keep the caret row on screen past
51
51
  * MAX_VISIBLE_LINES. Enter submits, backspace/delete edit, ←/→ move the
52
- * cursor, Tab completes the selected command, Ctrl+X opens the draft in the
52
+ * cursor, Tab completes the selected command, Ctrl+G opens the draft in the
53
53
  * external editor ($VISUAL/$EDITOR), Escape clears (or closes the help
54
54
  * menu), `?` toggles the help menu. Windows ConPTY pipelines deliver
55
55
  * whole lines with the Enter key lost: a trailing CR/LF in the input marks
@@ -1 +1 @@
1
- {"version":3,"file":"PromptInput.d.ts","sourceRoot":"","sources":["../../../src/components/PromptInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAazB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAkDxD;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,IAAI,OAAO,CAAA;IAClB,KAAK,IAAI,IAAI,CAAA;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAA;IAChB,0EAA0E;IAC1E,QAAQ,EAAE,OAAO,CAAA;IACjB,YAAY,IAAI,IAAI,CAAA;IACpB;;;OAGG;IACH,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAA;IACrD,6EAA6E;IAC7E,eAAe,EAAE,OAAO,CAAA;IACxB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,cAAc,CAAC,IAAI,IAAI,CAAA;IACvB,8EAA8E;IAC9E,eAAe,CAAC,IAAI,IAAI,CAAA;IACxB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;CACzD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,cAAc,EACd,eAAe,EACf,aAAa,GACd,EAAE,gBAAgB,qBA46BlB"}
1
+ {"version":3,"file":"PromptInput.d.ts","sourceRoot":"","sources":["../../../src/components/PromptInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAazB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;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+X opens the draft in the
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+X round-trip). */
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 or plugin-registered) when the input
321
- * resolves to one: the name parses as the first token so `/plan off`
322
- * dispatches `plan` with its argument text, and the merged command list
323
- * (locals + registry) decides whether the line is a command at all.
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
- // Ctrl+X / Cmd+X: edit the current draft in $VISUAL/$EDITOR (issue #123,
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+X stays locked forever.
461
- if (isMod(key) && input === 'x') {
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. Every other
525
- // input keeps the steer behavior so /new /model etc. stay idle-only.
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?.name === 'btw' && channel.commandList.some(c => c.name === 'btw')) {
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 = 13(ModelPicker 同款)。
41
- const { start, end } = listWindow(skills.map(() => 2), focusIndex, Math.max(terminalRows - 13, 2));
40
+ // 框架行:浮层预留 8 + Pane 2 + 标题 2 + 页脚 1 + 挂载包裹 marginTop 1 = 14(ModelPicker 同款)。
41
+ const { start, end } = listWindow(skills.map(() => 2), focusIndex, Math.max(terminalRows - 14, 2));
42
42
  return (_jsxs(Pane, { color: "permission", children: [_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "remember", bold: true, children: t('picker-title-skills') }) }), skills.length === 0 ? (_jsx(Text, { dimColor: true, children: t('skills-empty') })) : (skills.slice(start, end).map((skill, index) => {
43
43
  const absoluteIndex = start + index;
44
44
  return (_jsx(ListItem, { isFocused: absoluteIndex === focusIndex, description: `${sourceLabel(skill.source)}${skill.description === '' ? '' : ` · ${skill.description}`}`, showScrollUp: absoluteIndex === start && start > 0, showScrollDown: absoluteIndex === end - 1 && end < skills.length, children: skill.userInvocable ? `/${skill.name}` : skill.name }, skill.name));
@@ -1,20 +1,8 @@
1
1
  import React from 'react';
2
2
  import type { ToolFileDiff } from '../dsh-adapter/channel.js';
3
- import { type CliHighlight } from '../cc/cliHighlight.js';
4
3
  export { chalkFromToken } from '../cc/syntaxTheme.js';
4
+ export { parseAnsiRuns, highlightLines } from '../cc/syntaxRuns.js';
5
5
  import type { ToolBackground } from '../tuiDisplayPrefs.js';
6
- /** Parse a cli-highlight ANSI string into colored runs, preserving open
7
- * spans across embedded newlines (multi-line strings/comments). Handles
8
- * truecolor 38;2, 256-color 38;5 (tmux / FORCE_COLOR=2, issue #250 P1-1),
9
- * and single-code 16-color SGR. */
10
- export declare function parseAnsiRuns(text: string): {
11
- text: string;
12
- color?: string;
13
- }[];
14
- export declare function highlightLines(text: string, language: string | undefined, hl: CliHighlight | null, chTheme: Record<string, (text: string) => string> | undefined, themeSig: string): readonly {
15
- text: string;
16
- color?: string;
17
- }[][] | undefined;
18
6
  export declare function SplitDiffView({ diffs, width, maxRows, verbose, toolBackground, }: {
19
7
  readonly diffs: readonly ToolFileDiff[];
20
8
  /** Content width available to the whole two-pane block (divider included). */
@@ -1 +1 @@
1
- {"version":3,"file":"SplitDiffView.d.ts","sourceRoot":"","sources":["../../../src/components/SplitDiffView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAKzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAE7D,OAAO,EAA0B,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAIjF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAGrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAgE3D;;;oCAGoC;AACpC,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,CAuB9E;AAuBD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,EAAE,EAAE,YAAY,GAAG,IAAI,EACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,SAAS,EAC7D,QAAQ,EAAE,MAAM,GACf,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,GAAG,SAAS,CAe3D;AAsOD,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,KAAK,EACL,OAAO,EACP,OAAO,EACP,cAAuB,GACxB,EAAE;IACD,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAA;IACvC,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAA;CACzC,GAAG,KAAK,CAAC,SAAS,CAiGlB"}
1
+ {"version":3,"file":"SplitDiffView.d.ts","sourceRoot":"","sources":["../../../src/components/SplitDiffView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAIzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAO7D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAGnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAuR3D,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,KAAK,EACL,OAAO,EACP,OAAO,EACP,cAAuB,GACxB,EAAE;IACD,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAA;IACvC,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAA;CACzC,GAAG,KAAK,CAAC,SAAS,CAkFlB"}
@@ -4,98 +4,15 @@ import { Box, Text } from '../ui.js';
4
4
  import * as JsDiff from 'diff';
5
5
  import { extname } from 'node:path';
6
6
  import { getCliHighlightPromise } from '../cc/cliHighlight.js';
7
- import { SYNTAX_CLASS_TO_TOKEN, chalkFromToken } from '../cc/syntaxTheme.js';
7
+ import { highlightLines, syntaxThemeSignature } from '../cc/syntaxRuns.js';
8
8
  // Backward-compatible re-export: repro scripts import chalkFromToken from
9
9
  // this module's old home.
10
10
  export { chalkFromToken } from '../cc/syntaxTheme.js';
11
+ export { parseAnsiRuns, highlightLines } from '../cc/syntaxRuns.js';
11
12
  import { getTheme } from '../theme.js';
12
13
  import { useTheme } from './design-system/ThemeProvider.js';
13
14
  /** Replace tabs so width math and alignment hold (pi convention). */
14
15
  const expandTabs = (text) => text.replaceAll('\t', ' ');
15
- // --- syntax highlighting ----------------------------------------------------
16
- // The hljs-class → theme-token map and chalkFromToken live in
17
- // ../cc/syntaxTheme.ts (shared with the markdown code-block renderer).
18
- /** ANSI 16-color SGR codes → rgb() strings (the dark-ansi palette path). */
19
- const ANSI16 = {
20
- 30: 'rgb(0,0,0)', 31: 'rgb(128,0,0)', 32: 'rgb(0,128,0)', 33: 'rgb(128,128,0)',
21
- 34: 'rgb(0,0,128)', 35: 'rgb(128,0,128)', 36: 'rgb(0,128,128)', 37: 'rgb(192,192,192)',
22
- 90: 'rgb(128,128,128)', 91: 'rgb(255,0,0)', 92: 'rgb(0,255,0)', 93: 'rgb(255,255,0)',
23
- 94: 'rgb(0,0,255)', 95: 'rgb(255,0,255)', 96: 'rgb(0,255,255)', 97: 'rgb(255,255,255)',
24
- };
25
- /** Parse a cli-highlight ANSI string into colored runs, preserving open
26
- * spans across embedded newlines (multi-line strings/comments). Handles
27
- * truecolor 38;2, 256-color 38;5 (tmux / FORCE_COLOR=2, issue #250 P1-1),
28
- * and single-code 16-color SGR. */
29
- export function parseAnsiRuns(text) {
30
- const runs = [];
31
- let color;
32
- let rest = text;
33
- // eslint-disable-next-line no-control-regex -- parsing SGR is the point
34
- const sgr = /\x1b\[([0-9;]+)m/;
35
- while (rest.length > 0) {
36
- const match = sgr.exec(rest);
37
- const chunk = match === null ? rest : rest.slice(0, match.index);
38
- if (chunk !== '')
39
- runs.push(color === undefined ? { text: chunk } : { text: chunk, color });
40
- if (match === null)
41
- break;
42
- const codes = match[1].split(';').map(Number);
43
- if (codes.includes(0) || codes.includes(39))
44
- color = undefined;
45
- else if (codes[0] === 38 && codes[1] === 2 && codes.length >= 5) {
46
- color = `rgb(${codes[2]},${codes[3]},${codes[4]})`;
47
- }
48
- else if (codes[0] === 38 && codes[1] === 5 && codes.length >= 3) {
49
- color = `ansi256(${codes[2]})`;
50
- }
51
- else if (codes.length === 1 && ANSI16[codes[0]] !== undefined) {
52
- color = ANSI16[codes[0]];
53
- }
54
- rest = rest.slice(match.index + match[0].length);
55
- }
56
- return runs;
57
- }
58
- /** Split flat runs into per-line run arrays at embedded newlines. */
59
- function splitRunsToLines(runs) {
60
- const lines = [[]];
61
- for (const run of runs) {
62
- const parts = run.text.split('\n');
63
- for (let i = 0; i < parts.length; i++) {
64
- if (i > 0)
65
- lines.push([]);
66
- if (parts[i] !== '') {
67
- lines[lines.length - 1].push(run.color === undefined ? { text: parts[i] } : { text: parts[i], color: run.color });
68
- }
69
- }
70
- }
71
- return lines;
72
- }
73
- /** Whole-hunk syntax runs per line, cached per (theme signature + language
74
- * + text): one entry survives any amount of card re-rendering, and the
75
- * palette change invalidates it (issue #250, P1-2 + P2-8). */
76
- const syntaxCache = new Map();
77
- const SYNTAX_CACHE_MAX = 40;
78
- export function highlightLines(text, language, hl, chTheme, themeSig) {
79
- if (hl === null || chTheme === undefined || language === undefined)
80
- return undefined;
81
- if (text === '' || !hl.supportsLanguage(language))
82
- return undefined;
83
- const key = `${themeSig}${language}${text}`;
84
- const cached = syntaxCache.get(key);
85
- if (cached !== undefined)
86
- return cached;
87
- let lines;
88
- try {
89
- lines = splitRunsToLines(parseAnsiRuns(hl.highlight(text, { language, theme: chTheme })));
90
- }
91
- catch {
92
- return undefined;
93
- }
94
- if (syntaxCache.size >= SYNTAX_CACHE_MAX)
95
- syntaxCache.clear();
96
- syntaxCache.set(key, lines);
97
- return lines;
98
- }
99
16
  /**
100
17
  * Merge syntax runs with word-diff change flags over the same string: both
101
18
  * partition it, so an offset sweep yields runs carrying a syntax color AND
@@ -306,24 +223,8 @@ export function SplitDiffView({ diffs, width, maxRows, verbose, toolBackground =
306
223
  const [themeName] = useTheme();
307
224
  // Resolved-palette signature: covers dark→light, light→dark, AND the
308
225
  // `auto` base flip where the name never changes (issue #250, P1-2).
309
- const themeSig = React.useMemo(() => {
310
- const theme = getTheme(themeName);
311
- return Object.values(SYNTAX_CLASS_TO_TOKEN)
312
- .map(key => theme[key] ?? '')
313
- .join('|');
314
- }, [themeName]);
315
- const chTheme = React.useMemo(() => {
316
- const theme = getTheme(themeName);
317
- const out = {};
318
- for (const [tokenClass, tokenKey] of Object.entries(SYNTAX_CLASS_TO_TOKEN)) {
319
- const value = theme[tokenKey];
320
- if (value !== undefined)
321
- out[tokenClass] = chalkFromToken(value);
322
- }
323
- return out;
324
- // themeSig changes exactly when any syntax token's resolved value does.
325
- // eslint-disable-next-line react-hooks/exhaustive-deps
326
- }, [themeSig]);
226
+ const syntaxTheme = React.useMemo(() => getTheme(themeName), [themeName]);
227
+ const themeSig = React.useMemo(() => syntaxThemeSignature(syntaxTheme), [syntaxTheme]);
327
228
  // Alignment is text-only and capped BEFORE styling: the highlighter and
328
229
  // word-diff work lands only on the visible slice (issue #250, P2-8).
329
230
  const rows = [];
@@ -348,8 +249,8 @@ export function SplitDiffView({ diffs, width, maxRows, verbose, toolBackground =
348
249
  const fileSyntax = diffs.map(diff => {
349
250
  const language = extname(diff.path).replace(/^\./, '') || undefined;
350
251
  return {
351
- old: highlightLines(expandTabs(diff.oldText ?? ''), language, hl, chTheme, themeSig),
352
- next: highlightLines(expandTabs(diff.newText), language, hl, chTheme, themeSig),
252
+ old: highlightLines(expandTabs(diff.oldText ?? ''), language, hl, syntaxTheme, themeSig),
253
+ next: highlightLines(expandTabs(diff.newText), language, hl, syntaxTheme, themeSig),
353
254
  };
354
255
  });
355
256
  return (_jsxs(Box, { flexDirection: "column", width: paneWidth * 2 + 1, children: [visible.map((row, index) => {
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import type { SubagentState } from '../dsh-adapter/subagents.js';
3
+ export interface SubagentCardProps {
4
+ subagent: SubagentState;
5
+ focused?: boolean;
6
+ onClick?: () => void;
7
+ }
8
+ export declare function SubagentCard({ subagent, focused, onClick }: SubagentCardProps): React.ReactNode;
9
+ //# sourceMappingURL=SubagentCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SubagentCard.d.ts","sourceRoot":"","sources":["../../../src/components/SubagentCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAShE,MAAM,WAAW,iBAAiB;IAAG,QAAQ,EAAE,aAAa,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE;AAEvG,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,iBAAiB,GAAG,KAAK,CAAC,SAAS,CA4B/F"}
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from '../ui.js';
3
+ import { t } from '../i18n.js';
4
+ import { isMinimalMode } from '../minimalMode.js';
5
+ function formatDuration(ms) {
6
+ const seconds = Math.floor(ms / 1000);
7
+ return seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}m${seconds % 60}s`;
8
+ }
9
+ export function SubagentCard({ subagent, focused, onClick }) {
10
+ const running = subagent.status === 'running' || subagent.status === 'starting';
11
+ const elapsed = subagent.completedAt ? subagent.completedAt - subagent.startedAt : Date.now() - subagent.startedAt;
12
+ const total = subagent.tokens?.total ?? ((subagent.tokens?.input ?? 0) + (subagent.tokens?.output ?? 0) || 0);
13
+ // Live preview: the newest streamed line rides under the header while the
14
+ // subagent runs, then folds away — the dashboard stays one line per settled
15
+ // subagent.
16
+ const liveLine = running ? subagent.output[subagent.output.length - 1] : undefined;
17
+ const minimal = isMinimalMode();
18
+ const glyph = running ? (minimal ? '·' : '🟡')
19
+ : subagent.status === 'failed' || subagent.status === 'cancelled' ? (minimal ? '×' : '🔴')
20
+ : (minimal ? '✓' : '🟢');
21
+ const glyphColor = minimal ? undefined
22
+ : running ? 'warning'
23
+ : subagent.status === 'failed' || subagent.status === 'cancelled' ? 'error'
24
+ : 'success';
25
+ return _jsxs(Box, { flexDirection: "column", paddingLeft: 1, marginBottom: 1, onClick: onClick, children: [_jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Text, { color: glyphColor, children: glyph }), _jsx(Text, { bold: true, color: focused ? 'claude' : undefined, children: `${t('subagent-card-prefix')}${subagent.description}` }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: ' · ' }), _jsx(Text, { children: subagent.model ?? subagent.provider ?? 'default' }), _jsx(Text, { dimColor: true, children: ` · ${formatDuration(elapsed)} · ${total || '—'} tok · ${subagent.toolCalls.length} tools` })] })] }), liveLine && _jsx(Text, { dimColor: true, wrap: "truncate", children: ` │ ${liveLine}` })] });
26
+ }
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import type { SubagentState } from '../dsh-adapter/subagents.js';
3
+ export interface SubagentDashboardProps {
4
+ subagents: SubagentState[];
5
+ onClose: () => void;
6
+ onSelect?: (agentId: string) => void;
7
+ }
8
+ /**
9
+ * SubagentDashboard — overlay panel showing all active/recent subagents.
10
+ * Keyboard: up/down to navigate, Enter to view detail, Esc to close.
11
+ */
12
+ export declare function SubagentDashboard({ subagents, onClose, onSelect }: SubagentDashboardProps): React.ReactNode;
13
+ //# sourceMappingURL=SubagentDashboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SubagentDashboard.d.ts","sourceRoot":"","sources":["../../../src/components/SubagentDashboard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAMhE,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,aAAa,EAAE,CAAA;IAC1B,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CACrC;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,OAAO,EACP,QAAQ,EACT,EAAE,sBAAsB,GAAG,KAAK,CAAC,SAAS,CAoG1C"}
@@ -0,0 +1,50 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { Box, Text, useInput, ScrollBox, useTerminalSize } from '../ui.js';
4
+ import { SubagentCard } from './SubagentCard.js';
5
+ import { t } from '../i18n.js';
6
+ import { Divider } from './design-system/Divider.js';
7
+ import { isPlainReturnInput } from '../utils/modifiers.js';
8
+ /**
9
+ * SubagentDashboard — overlay panel showing all active/recent subagents.
10
+ * Keyboard: up/down to navigate, Enter to view detail, Esc to close.
11
+ */
12
+ export function SubagentDashboard({ subagents, onClose, onSelect }) {
13
+ const [focusIndex, setFocusIndex] = React.useState(0);
14
+ const scrollRef = React.useRef(null);
15
+ const { rows, columns } = useTerminalSize();
16
+ useInput((input, key, event) => {
17
+ if (key.escape || (key.ctrl && input === 'c')) {
18
+ event.stopImmediatePropagation();
19
+ onClose();
20
+ return;
21
+ }
22
+ if (key.upArrow) {
23
+ event.stopImmediatePropagation();
24
+ setFocusIndex(i => Math.max(0, i - 1));
25
+ scrollRef.current?.scrollBy(-3);
26
+ return;
27
+ }
28
+ if (key.downArrow) {
29
+ event.stopImmediatePropagation();
30
+ setFocusIndex(i => Math.min(subagents.length - 1, i + 1));
31
+ scrollRef.current?.scrollBy(3);
32
+ return;
33
+ }
34
+ if (isPlainReturnInput(input, key) && onSelect) {
35
+ event.stopImmediatePropagation();
36
+ const selected = subagents[focusIndex];
37
+ if (selected)
38
+ onSelect(selected.agentId);
39
+ return;
40
+ }
41
+ // Consume all input while dashboard is open
42
+ event.stopImmediatePropagation();
43
+ });
44
+ const running = subagents.filter(s => s.status === 'running').length;
45
+ const completed = subagents.filter(s => s.status === 'completed').length;
46
+ const failed = subagents.filter(s => s.status === 'failed').length;
47
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [_jsx(Divider, { color: "claude", title: t('subagent-dashboard-title'), padding: 4 }), _jsxs(Box, { flexDirection: "row", gap: 3, marginTop: 1, marginBottom: 1, children: [_jsxs(Text, { children: [_jsx(Text, { color: "claude", children: running }), _jsxs(Text, { dimColor: true, children: [" ", t('subagent-count-running')] })] }), _jsxs(Text, { children: [_jsx(Text, { color: "success", children: completed }), _jsxs(Text, { dimColor: true, children: [" ", t('subagent-count-completed')] })] }), failed > 0 && (_jsxs(Text, { children: [_jsx(Text, { color: "error", children: failed }), _jsxs(Text, { dimColor: true, children: [" ", t('subagent-count-failed')] })] }))] }), _jsx(Box, { flexDirection: "column", maxHeight: Math.max(10, rows - 10), marginTop: 1, children: _jsx(ScrollBox, { ref: scrollRef, flexDirection: "column", flexGrow: 1, children: subagents.length === 0 ? (_jsxs(Box, { flexDirection: "column", alignItems: "center", marginTop: Math.max(2, Math.floor((rows - 16) / 3)), children: [_jsx(Text, { dimColor: true, children: '○' }), _jsx(Text, { dimColor: true, children: t('subagent-none') }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: t('subagent-empty-hint') }) })] })) : (subagents.map((subagent, index) => (_jsxs(Box, { flexDirection: "column", children: [_jsx(SubagentCard, { subagent: subagent, focused: index === focusIndex }), index < subagents.length - 1 && (_jsx(Text, { dimColor: true, children: '─'.repeat(Math.max(20, Math.min(72, columns - 6))) }))] }, subagent.agentId)))) }) }), _jsx(Divider, { color: "subtle", title: "", padding: 4 }), _jsx(Box, { marginTop: 0, children: _jsx(Text, { dimColor: true, children: onSelect
48
+ ? t('subagent-dashboard-hint-detail')
49
+ : t('subagent-dashboard-hint-basic') }) })] }));
50
+ }
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import type { SubagentState } from '../dsh-adapter/subagents.js';
3
+ export interface SubagentDetailSceneProps {
4
+ subagent: SubagentState;
5
+ onBack: () => void;
6
+ onInterrupt?: (agentId: string) => void;
7
+ }
8
+ /**
9
+ * SubagentDetailScene — full-screen paged detail view for one subagent.
10
+ * Header block (identity + stats) stays fixed; the body pages through
11
+ * 摘要 / 输出 / 工具 with ←/→. Follow-up delivery was removed: the official
12
+ * seam only accepts continuable children, and one-shot spawn children are
13
+ * disposed at settlement, so the affordance would be a dead control.
14
+ */
15
+ export declare function SubagentDetailScene({ subagent, onBack, onInterrupt, }: SubagentDetailSceneProps): React.ReactNode;
16
+ //# sourceMappingURL=SubagentDetailScene.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SubagentDetailScene.d.ts","sourceRoot":"","sources":["../../../src/components/SubagentDetailScene.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAsGhE,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,aAAa,CAAA;IACvB,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CACxC;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,QAAQ,EACR,MAAM,EACN,WAAW,GACZ,EAAE,wBAAwB,GAAG,KAAK,CAAC,SAAS,CA+L5C"}