@deepseek-harness-tui/dsh-tui 0.5.0
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/LICENSE +21 -0
- package/README.md +235 -0
- package/bin/dsh-tui.js +95 -0
- package/cordis.patch.yml +218 -0
- package/cordis.yml +185 -0
- package/lib/invariant.js +28 -0
- package/lib/types/activityPrefs.d.ts +29 -0
- package/lib/types/activityPrefs.d.ts.map +1 -0
- package/lib/types/activityPrefs.js +60 -0
- package/lib/types/bootstrap/state.d.ts +13 -0
- package/lib/types/bootstrap/state.d.ts.map +1 -0
- package/lib/types/bootstrap/state.js +12 -0
- package/lib/types/cc/cliHighlight.d.ts +20 -0
- package/lib/types/cc/cliHighlight.d.ts.map +1 -0
- package/lib/types/cc/cliHighlight.js +53 -0
- package/lib/types/cc/figures.d.ts +59 -0
- package/lib/types/cc/figures.d.ts.map +1 -0
- package/lib/types/cc/figures.js +69 -0
- package/lib/types/cc/format.d.ts +26 -0
- package/lib/types/cc/format.d.ts.map +1 -0
- package/lib/types/cc/format.js +55 -0
- package/lib/types/cc/hash.d.ts +25 -0
- package/lib/types/cc/hash.d.ts.map +1 -0
- package/lib/types/cc/hash.js +41 -0
- package/lib/types/cc/hyperlink.d.ts +21 -0
- package/lib/types/cc/hyperlink.d.ts.map +1 -0
- package/lib/types/cc/hyperlink.js +31 -0
- package/lib/types/cc/markdown.d.ts +43 -0
- package/lib/types/cc/markdown.d.ts.map +1 -0
- package/lib/types/cc/markdown.js +345 -0
- package/lib/types/cc/spinnerVerbs.d.ts +3 -0
- package/lib/types/cc/spinnerVerbs.d.ts.map +1 -0
- package/lib/types/cc/spinnerVerbs.js +190 -0
- package/lib/types/cc/terminal.d.ts +15 -0
- package/lib/types/cc/terminal.d.ts.map +1 -0
- package/lib/types/cc/terminal.js +84 -0
- package/lib/types/channel.d.ts +672 -0
- package/lib/types/channel.d.ts.map +1 -0
- package/lib/types/channel.js +2427 -0
- package/lib/types/childStderr.d.ts +63 -0
- package/lib/types/childStderr.d.ts.map +1 -0
- package/lib/types/childStderr.js +161 -0
- package/lib/types/commands.d.ts +55 -0
- package/lib/types/commands.d.ts.map +1 -0
- package/lib/types/commands.js +101 -0
- package/lib/types/components/ActivityLine.d.ts +29 -0
- package/lib/types/components/ActivityLine.d.ts.map +1 -0
- package/lib/types/components/ActivityLine.js +47 -0
- package/lib/types/components/ActivityPicker.d.ts +13 -0
- package/lib/types/components/ActivityPicker.d.ts.map +1 -0
- package/lib/types/components/ActivityPicker.js +24 -0
- package/lib/types/components/CommandSuggestions.d.ts +14 -0
- package/lib/types/components/CommandSuggestions.d.ts.map +1 -0
- package/lib/types/components/CommandSuggestions.js +33 -0
- package/lib/types/components/FileSuggestions.d.ts +12 -0
- package/lib/types/components/FileSuggestions.d.ts.map +1 -0
- package/lib/types/components/FileSuggestions.js +26 -0
- package/lib/types/components/GoalTodoPanel.d.ts +13 -0
- package/lib/types/components/GoalTodoPanel.d.ts.map +1 -0
- package/lib/types/components/GoalTodoPanel.js +57 -0
- package/lib/types/components/HelpMenu.d.ts +12 -0
- package/lib/types/components/HelpMenu.d.ts.map +1 -0
- package/lib/types/components/HelpMenu.js +12 -0
- package/lib/types/components/HistorySearchDialog.d.ts +16 -0
- package/lib/types/components/HistorySearchDialog.d.ts.map +1 -0
- package/lib/types/components/HistorySearchDialog.js +31 -0
- package/lib/types/components/InterruptedByUser.d.ts +7 -0
- package/lib/types/components/InterruptedByUser.d.ts.map +1 -0
- package/lib/types/components/InterruptedByUser.js +9 -0
- package/lib/types/components/LoadedContextPanel.d.ts +20 -0
- package/lib/types/components/LoadedContextPanel.d.ts.map +1 -0
- package/lib/types/components/LoadedContextPanel.js +30 -0
- package/lib/types/components/LogoV2.d.ts +23 -0
- package/lib/types/components/LogoV2.d.ts.map +1 -0
- package/lib/types/components/LogoV2.js +95 -0
- package/lib/types/components/Markdown.d.ts +24 -0
- package/lib/types/components/Markdown.d.ts.map +1 -0
- package/lib/types/components/Markdown.js +106 -0
- package/lib/types/components/MarkdownTable.d.ts +17 -0
- package/lib/types/components/MarkdownTable.d.ts.map +1 -0
- package/lib/types/components/MarkdownTable.js +272 -0
- package/lib/types/components/MessageList.d.ts +41 -0
- package/lib/types/components/MessageList.d.ts.map +1 -0
- package/lib/types/components/MessageList.js +303 -0
- package/lib/types/components/ModelPicker.d.ts +14 -0
- package/lib/types/components/ModelPicker.d.ts.map +1 -0
- package/lib/types/components/ModelPicker.js +15 -0
- package/lib/types/components/PresetPicker.d.ts +17 -0
- package/lib/types/components/PresetPicker.d.ts.map +1 -0
- package/lib/types/components/PresetPicker.js +25 -0
- package/lib/types/components/PromptInput.d.ts +67 -0
- package/lib/types/components/PromptInput.d.ts.map +1 -0
- package/lib/types/components/PromptInput.js +778 -0
- package/lib/types/components/ResumePicker.d.ts +15 -0
- package/lib/types/components/ResumePicker.d.ts.map +1 -0
- package/lib/types/components/ResumePicker.js +34 -0
- package/lib/types/components/RewindPicker.d.ts +14 -0
- package/lib/types/components/RewindPicker.d.ts.map +1 -0
- package/lib/types/components/RewindPicker.js +23 -0
- package/lib/types/components/SearchBox.d.ts +20 -0
- package/lib/types/components/SearchBox.d.ts.map +1 -0
- package/lib/types/components/SearchBox.js +33 -0
- package/lib/types/components/Select.d.ts +22 -0
- package/lib/types/components/Select.d.ts.map +1 -0
- package/lib/types/components/Select.js +19 -0
- package/lib/types/components/Spinner/GlimmerMessage.d.ts +19 -0
- package/lib/types/components/Spinner/GlimmerMessage.d.ts.map +1 -0
- package/lib/types/components/Spinner/GlimmerMessage.js +78 -0
- package/lib/types/components/Spinner/SpinnerAnimationRow.d.ts +31 -0
- package/lib/types/components/Spinner/SpinnerAnimationRow.d.ts.map +1 -0
- package/lib/types/components/Spinner/SpinnerAnimationRow.js +162 -0
- package/lib/types/components/Spinner/SpinnerGlyph.d.ts +16 -0
- package/lib/types/components/Spinner/SpinnerGlyph.d.ts.map +1 -0
- package/lib/types/components/Spinner/SpinnerGlyph.js +36 -0
- package/lib/types/components/Spinner/spinnerMode.d.ts +6 -0
- package/lib/types/components/Spinner/spinnerMode.d.ts.map +1 -0
- package/lib/types/components/Spinner/spinnerMode.js +1 -0
- package/lib/types/components/Spinner/spinnerUtils.d.ts +41 -0
- package/lib/types/components/Spinner/spinnerUtils.d.ts.map +1 -0
- package/lib/types/components/Spinner/spinnerUtils.js +104 -0
- package/lib/types/components/Spinner/useStalledAnimation.d.ts +16 -0
- package/lib/types/components/Spinner/useStalledAnimation.d.ts.map +1 -0
- package/lib/types/components/Spinner/useStalledAnimation.js +71 -0
- package/lib/types/components/StreamingMarkdown.d.ts +12 -0
- package/lib/types/components/StreamingMarkdown.d.ts.map +1 -0
- package/lib/types/components/StreamingMarkdown.js +39 -0
- package/lib/types/components/ThemePicker.d.ts +22 -0
- package/lib/types/components/ThemePicker.d.ts.map +1 -0
- package/lib/types/components/ThemePicker.js +51 -0
- package/lib/types/components/ThinkingToggle.d.ts +17 -0
- package/lib/types/components/ThinkingToggle.d.ts.map +1 -0
- package/lib/types/components/ThinkingToggle.js +30 -0
- package/lib/types/components/ToolUseLoader.d.ts +13 -0
- package/lib/types/components/ToolUseLoader.d.ts.map +1 -0
- package/lib/types/components/ToolUseLoader.js +17 -0
- package/lib/types/components/Whale.d.ts +22 -0
- package/lib/types/components/Whale.d.ts.map +1 -0
- package/lib/types/components/Whale.js +78 -0
- package/lib/types/components/WorkingSpinner.d.ts +26 -0
- package/lib/types/components/WorkingSpinner.d.ts.map +1 -0
- package/lib/types/components/WorkingSpinner.js +68 -0
- package/lib/types/components/activityFrames.d.ts +31 -0
- package/lib/types/components/activityFrames.d.ts.map +1 -0
- package/lib/types/components/activityFrames.js +83 -0
- package/lib/types/components/bigfont.d.ts +28 -0
- package/lib/types/components/bigfont.d.ts.map +1 -0
- package/lib/types/components/bigfont.js +91 -0
- package/lib/types/components/design-system/Byline.d.ts +14 -0
- package/lib/types/components/design-system/Byline.d.ts.map +1 -0
- package/lib/types/components/design-system/Byline.js +17 -0
- package/lib/types/components/design-system/Divider.d.ts +40 -0
- package/lib/types/components/design-system/Divider.d.ts.map +1 -0
- package/lib/types/components/design-system/Divider.js +24 -0
- package/lib/types/components/design-system/KeyboardShortcutHint.d.ts +19 -0
- package/lib/types/components/design-system/KeyboardShortcutHint.d.ts.map +1 -0
- package/lib/types/components/design-system/KeyboardShortcutHint.js +14 -0
- package/lib/types/components/design-system/ListItem.d.ts +36 -0
- package/lib/types/components/design-system/ListItem.d.ts.map +1 -0
- package/lib/types/components/design-system/ListItem.js +48 -0
- package/lib/types/components/design-system/LoadingState.d.ts +19 -0
- package/lib/types/components/design-system/LoadingState.d.ts.map +1 -0
- package/lib/types/components/design-system/LoadingState.js +22 -0
- package/lib/types/components/design-system/Pane.d.ts +17 -0
- package/lib/types/components/design-system/Pane.d.ts.map +1 -0
- package/lib/types/components/design-system/Pane.js +15 -0
- package/lib/types/components/design-system/ProgressBar.d.ts +22 -0
- package/lib/types/components/design-system/ProgressBar.d.ts.map +1 -0
- package/lib/types/components/design-system/ProgressBar.js +27 -0
- package/lib/types/components/design-system/StatusIcon.d.ts +15 -0
- package/lib/types/components/design-system/StatusIcon.d.ts.map +1 -0
- package/lib/types/components/design-system/StatusIcon.js +20 -0
- package/lib/types/components/design-system/ThemeProvider.d.ts +11 -0
- package/lib/types/components/design-system/ThemeProvider.d.ts.map +1 -0
- package/lib/types/components/design-system/ThemeProvider.js +135 -0
- package/lib/types/components/design-system/ThemedBox.d.ts +37 -0
- package/lib/types/components/design-system/ThemedBox.d.ts.map +1 -0
- package/lib/types/components/design-system/ThemedBox.js +26 -0
- package/lib/types/components/design-system/ThemedText.d.ts +56 -0
- package/lib/types/components/design-system/ThemedText.d.ts.map +1 -0
- package/lib/types/components/design-system/ThemedText.js +45 -0
- package/lib/types/components/messages/AssistantTextMessage.d.ts +18 -0
- package/lib/types/components/messages/AssistantTextMessage.d.ts.map +1 -0
- package/lib/types/components/messages/AssistantTextMessage.js +15 -0
- package/lib/types/components/messages/AssistantThinkingMessage.d.ts +23 -0
- package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -0
- package/lib/types/components/messages/AssistantThinkingMessage.js +23 -0
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts +23 -0
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -0
- package/lib/types/components/messages/AssistantToolUseMessage.js +220 -0
- package/lib/types/components/messages/MessageMetadata.d.ts +15 -0
- package/lib/types/components/messages/MessageMetadata.d.ts.map +1 -0
- package/lib/types/components/messages/MessageMetadata.js +20 -0
- package/lib/types/components/messages/UserPromptMessage.d.ts +19 -0
- package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -0
- package/lib/types/components/messages/UserPromptMessage.js +15 -0
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts +39 -0
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -0
- package/lib/types/components/questions/AskUserQuestionPanel.js +228 -0
- package/lib/types/components/shimmer.d.ts +35 -0
- package/lib/types/components/shimmer.d.ts.map +1 -0
- package/lib/types/components/shimmer.js +43 -0
- package/lib/types/components/whaleFrames.d.ts +25 -0
- package/lib/types/components/whaleFrames.d.ts.map +1 -0
- package/lib/types/components/whaleFrames.js +419 -0
- package/lib/types/customTheme.d.ts +104 -0
- package/lib/types/customTheme.d.ts.map +1 -0
- package/lib/types/customTheme.js +268 -0
- package/lib/types/effortPrefs.d.ts +22 -0
- package/lib/types/effortPrefs.d.ts.map +1 -0
- package/lib/types/effortPrefs.js +45 -0
- package/lib/types/history.d.ts +24 -0
- package/lib/types/history.d.ts.map +1 -0
- package/lib/types/history.js +75 -0
- package/lib/types/hooks/useBlink.d.ts +11 -0
- package/lib/types/hooks/useBlink.d.ts.map +1 -0
- package/lib/types/hooks/useBlink.js +20 -0
- package/lib/types/i18n.d.ts +926 -0
- package/lib/types/i18n.d.ts.map +1 -0
- package/lib/types/i18n.js +356 -0
- package/lib/types/index.d.ts +70 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +31 -0
- package/lib/types/ink/Ansi.d.ts +11 -0
- package/lib/types/ink/Ansi.d.ts.map +1 -0
- package/lib/types/ink/Ansi.js +269 -0
- package/lib/types/ink/bidi.d.ts +18 -0
- package/lib/types/ink/bidi.d.ts.map +1 -0
- package/lib/types/ink/bidi.js +119 -0
- package/lib/types/ink/clearTerminal.d.ts +22 -0
- package/lib/types/ink/clearTerminal.d.ts.map +1 -0
- package/lib/types/ink/clearTerminal.js +72 -0
- package/lib/types/ink/colorize.d.ts +33 -0
- package/lib/types/ink/colorize.d.ts.map +1 -0
- package/lib/types/ink/colorize.js +213 -0
- package/lib/types/ink/components/AlternateScreen.d.ts +22 -0
- package/lib/types/ink/components/AlternateScreen.d.ts.map +1 -0
- package/lib/types/ink/components/AlternateScreen.js +74 -0
- package/lib/types/ink/components/App.d.ts +73 -0
- package/lib/types/ink/components/App.d.ts.map +1 -0
- package/lib/types/ink/components/App.js +597 -0
- package/lib/types/ink/components/AppContext.d.ts +15 -0
- package/lib/types/ink/components/AppContext.d.ts.map +1 -0
- package/lib/types/ink/components/AppContext.js +11 -0
- package/lib/types/ink/components/Box.d.ts +49 -0
- package/lib/types/ink/components/Box.d.ts.map +1 -0
- package/lib/types/ink/components/Box.js +153 -0
- package/lib/types/ink/components/Button.d.ts +37 -0
- package/lib/types/ink/components/Button.d.ts.map +1 -0
- package/lib/types/ink/components/Button.js +166 -0
- package/lib/types/ink/components/ClockContext.d.ts +10 -0
- package/lib/types/ink/components/ClockContext.d.ts.map +1 -0
- package/lib/types/ink/components/ClockContext.js +108 -0
- package/lib/types/ink/components/CursorDeclarationContext.d.ts +30 -0
- package/lib/types/ink/components/CursorDeclarationContext.d.ts.map +1 -0
- package/lib/types/ink/components/CursorDeclarationContext.js +6 -0
- package/lib/types/ink/components/ErrorOverview.d.ts +7 -0
- package/lib/types/ink/components/ErrorOverview.d.ts.map +1 -0
- package/lib/types/ink/components/ErrorOverview.js +49 -0
- package/lib/types/ink/components/Link.d.ts +8 -0
- package/lib/types/ink/components/Link.d.ts.map +1 -0
- package/lib/types/ink/components/Link.js +33 -0
- package/lib/types/ink/components/Newline.d.ts +13 -0
- package/lib/types/ink/components/Newline.d.ts.map +1 -0
- package/lib/types/ink/components/Newline.js +29 -0
- package/lib/types/ink/components/NoSelect.d.ts +20 -0
- package/lib/types/ink/components/NoSelect.d.ts.map +1 -0
- package/lib/types/ink/components/NoSelect.js +56 -0
- package/lib/types/ink/components/RawAnsi.d.ts +17 -0
- package/lib/types/ink/components/RawAnsi.d.ts.map +1 -0
- package/lib/types/ink/components/RawAnsi.js +45 -0
- package/lib/types/ink/components/ScrollBox.d.ts +78 -0
- package/lib/types/ink/components/ScrollBox.d.ts.map +1 -0
- package/lib/types/ink/components/ScrollBox.js +174 -0
- package/lib/types/ink/components/Spacer.d.ts +6 -0
- package/lib/types/ink/components/Spacer.d.ts.map +1 -0
- package/lib/types/ink/components/Spacer.js +19 -0
- package/lib/types/ink/components/StdinContext.d.ts +32 -0
- package/lib/types/ink/components/StdinContext.d.ts.map +1 -0
- package/lib/types/ink/components/StdinContext.js +16 -0
- package/lib/types/ink/components/TerminalFocusContext.d.ts +11 -0
- package/lib/types/ink/components/TerminalFocusContext.d.ts.map +1 -0
- package/lib/types/ink/components/TerminalFocusContext.js +45 -0
- package/lib/types/ink/components/TerminalSizeContext.d.ts +6 -0
- package/lib/types/ink/components/TerminalSizeContext.d.ts.map +1 -0
- package/lib/types/ink/components/TerminalSizeContext.js +3 -0
- package/lib/types/ink/components/Text.d.ts +56 -0
- package/lib/types/ink/components/Text.d.ts.map +1 -0
- package/lib/types/ink/components/Text.js +194 -0
- package/lib/types/ink/constants.d.ts +3 -0
- package/lib/types/ink/constants.d.ts.map +1 -0
- package/lib/types/ink/constants.js +2 -0
- package/lib/types/ink/cursor.d.ts +11 -0
- package/lib/types/ink/cursor.d.ts.map +1 -0
- package/lib/types/ink/cursor.js +1 -0
- package/lib/types/ink/devtools.d.ts +7 -0
- package/lib/types/ink/devtools.d.ts.map +1 -0
- package/lib/types/ink/devtools.js +1 -0
- package/lib/types/ink/dom.d.ts +178 -0
- package/lib/types/ink/dom.d.ts.map +1 -0
- package/lib/types/ink/dom.js +424 -0
- package/lib/types/ink/events/click-event.d.ts +32 -0
- package/lib/types/ink/events/click-event.d.ts.map +1 -0
- package/lib/types/ink/events/click-event.js +36 -0
- package/lib/types/ink/events/dispatcher.d.ts +63 -0
- package/lib/types/ink/events/dispatcher.d.ts.map +1 -0
- package/lib/types/ink/events/dispatcher.js +194 -0
- package/lib/types/ink/events/emitter.d.ts +20 -0
- package/lib/types/ink/events/emitter.d.ts.map +1 -0
- package/lib/types/ink/events/emitter.js +43 -0
- package/lib/types/ink/events/event-handlers.d.ts +47 -0
- package/lib/types/ink/events/event-handlers.d.ts.map +1 -0
- package/lib/types/ink/events/event-handlers.js +30 -0
- package/lib/types/ink/events/event.d.ts +19 -0
- package/lib/types/ink/events/event.d.ts.map +1 -0
- package/lib/types/ink/events/event.js +22 -0
- package/lib/types/ink/events/focus-event.d.ts +19 -0
- package/lib/types/ink/events/focus-event.d.ts.map +1 -0
- package/lib/types/ink/events/focus-event.js +21 -0
- package/lib/types/ink/events/input-event.d.ts +53 -0
- package/lib/types/ink/events/input-event.d.ts.map +1 -0
- package/lib/types/ink/events/input-event.js +180 -0
- package/lib/types/ink/events/keyboard-event.d.ts +40 -0
- package/lib/types/ink/events/keyboard-event.d.ts.map +1 -0
- package/lib/types/ink/events/keyboard-event.js +65 -0
- package/lib/types/ink/events/paste-event.d.ts +11 -0
- package/lib/types/ink/events/paste-event.d.ts.map +1 -0
- package/lib/types/ink/events/paste-event.js +13 -0
- package/lib/types/ink/events/resize-event.d.ts +13 -0
- package/lib/types/ink/events/resize-event.d.ts.map +1 -0
- package/lib/types/ink/events/resize-event.js +16 -0
- package/lib/types/ink/events/terminal-event.d.ts +112 -0
- package/lib/types/ink/events/terminal-event.d.ts.map +1 -0
- package/lib/types/ink/events/terminal-event.js +131 -0
- package/lib/types/ink/events/terminal-focus-event.d.ts +21 -0
- package/lib/types/ink/events/terminal-focus-event.d.ts.map +1 -0
- package/lib/types/ink/events/terminal-focus-event.js +18 -0
- package/lib/types/ink/focus.d.ts +74 -0
- package/lib/types/ink/focus.d.ts.map +1 -0
- package/lib/types/ink/focus.js +188 -0
- package/lib/types/ink/frame.d.ts +119 -0
- package/lib/types/ink/frame.d.ts.map +1 -0
- package/lib/types/ink/frame.js +43 -0
- package/lib/types/ink/get-max-width.d.ts +20 -0
- package/lib/types/ink/get-max-width.d.ts.map +1 -0
- package/lib/types/ink/get-max-width.js +25 -0
- package/lib/types/ink/hit-test.d.ts +47 -0
- package/lib/types/ink/hit-test.d.ts.map +1 -0
- package/lib/types/ink/hit-test.js +126 -0
- package/lib/types/ink/hooks/use-animation-frame.d.ts +27 -0
- package/lib/types/ink/hooks/use-animation-frame.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-animation-frame.js +48 -0
- package/lib/types/ink/hooks/use-app.d.ts +8 -0
- package/lib/types/ink/hooks/use-app.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-app.js +8 -0
- package/lib/types/ink/hooks/use-copy-on-select.d.ts +22 -0
- package/lib/types/ink/hooks/use-copy-on-select.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-copy-on-select.js +40 -0
- package/lib/types/ink/hooks/use-declared-cursor.d.ts +31 -0
- package/lib/types/ink/hooks/use-declared-cursor.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-declared-cursor.js +65 -0
- package/lib/types/ink/hooks/use-input.d.ts +41 -0
- package/lib/types/ink/hooks/use-input.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-input.js +73 -0
- package/lib/types/ink/hooks/use-interval.d.ts +23 -0
- package/lib/types/ink/hooks/use-interval.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-interval.js +58 -0
- package/lib/types/ink/hooks/use-search-highlight.d.ts +34 -0
- package/lib/types/ink/hooks/use-search-highlight.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-search-highlight.js +35 -0
- package/lib/types/ink/hooks/use-selection.d.ts +44 -0
- package/lib/types/ink/hooks/use-selection.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-selection.js +64 -0
- package/lib/types/ink/hooks/use-stdin.d.ts +8 -0
- package/lib/types/ink/hooks/use-stdin.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-stdin.js +8 -0
- package/lib/types/ink/hooks/use-tab-status.d.ts +18 -0
- package/lib/types/ink/hooks/use-tab-status.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-tab-status.js +58 -0
- package/lib/types/ink/hooks/use-terminal-focus.d.ts +11 -0
- package/lib/types/ink/hooks/use-terminal-focus.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-terminal-focus.js +15 -0
- package/lib/types/ink/hooks/use-terminal-size.d.ts +6 -0
- package/lib/types/ink/hooks/use-terminal-size.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-terminal-size.js +12 -0
- package/lib/types/ink/hooks/use-terminal-title.d.ts +14 -0
- package/lib/types/ink/hooks/use-terminal-title.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-terminal-title.js +30 -0
- package/lib/types/ink/hooks/use-terminal-viewport.d.ts +32 -0
- package/lib/types/ink/hooks/use-terminal-viewport.d.ts.map +1 -0
- package/lib/types/ink/hooks/use-terminal-viewport.js +79 -0
- package/lib/types/ink/ink.d.ts +370 -0
- package/lib/types/ink/ink.d.ts.map +1 -0
- package/lib/types/ink/ink.js +1665 -0
- package/lib/types/ink/instances.d.ts +10 -0
- package/lib/types/ink/instances.d.ts.map +1 -0
- package/lib/types/ink/instances.js +13 -0
- package/lib/types/ink/layout/engine.d.ts +7 -0
- package/lib/types/ink/layout/engine.d.ts.map +1 -0
- package/lib/types/ink/layout/engine.js +8 -0
- package/lib/types/ink/layout/geometry.d.ts +86 -0
- package/lib/types/ink/layout/geometry.d.ts.map +1 -0
- package/lib/types/ink/layout/geometry.js +95 -0
- package/lib/types/ink/layout/node.d.ts +151 -0
- package/lib/types/ink/layout/node.d.ts.map +1 -0
- package/lib/types/ink/layout/node.js +72 -0
- package/lib/types/ink/layout/yoga.d.ts +69 -0
- package/lib/types/ink/layout/yoga.d.ts.map +1 -0
- package/lib/types/ink/layout/yoga.js +249 -0
- package/lib/types/ink/line-width-cache.d.ts +9 -0
- package/lib/types/ink/line-width-cache.d.ts.map +1 -0
- package/lib/types/ink/line-width-cache.js +45 -0
- package/lib/types/ink/log-update.d.ts +57 -0
- package/lib/types/ink/log-update.d.ts.map +1 -0
- package/lib/types/ink/log-update.js +710 -0
- package/lib/types/ink/measure-element.d.ts +19 -0
- package/lib/types/ink/measure-element.d.ts.map +1 -0
- package/lib/types/ink/measure-element.js +10 -0
- package/lib/types/ink/measure-text.d.ts +13 -0
- package/lib/types/ink/measure-text.d.ts.map +1 -0
- package/lib/types/ink/measure-text.js +38 -0
- package/lib/types/ink/node-cache.d.ts +33 -0
- package/lib/types/ink/node-cache.d.ts.map +1 -0
- package/lib/types/ink/node-cache.js +42 -0
- package/lib/types/ink/optimizer.d.ts +18 -0
- package/lib/types/ink/optimizer.d.ts.map +1 -0
- package/lib/types/ink/optimizer.js +83 -0
- package/lib/types/ink/output.d.ts +200 -0
- package/lib/types/ink/output.d.ts.map +1 -0
- package/lib/types/ink/output.js +663 -0
- package/lib/types/ink/parse-keypress.d.ts +132 -0
- package/lib/types/ink/parse-keypress.d.ts.map +1 -0
- package/lib/types/ink/parse-keypress.js +707 -0
- package/lib/types/ink/reconciler.d.ts +43 -0
- package/lib/types/ink/reconciler.d.ts.map +1 -0
- package/lib/types/ink/reconciler.js +413 -0
- package/lib/types/ink/render-border.d.ts +36 -0
- package/lib/types/ink/render-border.d.ts.map +1 -0
- package/lib/types/ink/render-border.js +143 -0
- package/lib/types/ink/render-node-to-output.d.ts +93 -0
- package/lib/types/ink/render-node-to-output.d.ts.map +1 -0
- package/lib/types/ink/render-node-to-output.js +1290 -0
- package/lib/types/ink/render-to-screen.d.ts +65 -0
- package/lib/types/ink/render-to-screen.d.ts.map +1 -0
- package/lib/types/ink/render-to-screen.js +189 -0
- package/lib/types/ink/renderer.d.ts +24 -0
- package/lib/types/ink/renderer.d.ts.map +1 -0
- package/lib/types/ink/renderer.js +136 -0
- package/lib/types/ink/root.d.ts +91 -0
- package/lib/types/ink/root.d.ts.map +1 -0
- package/lib/types/ink/root.js +93 -0
- package/lib/types/ink/screen.d.ts +443 -0
- package/lib/types/ink/screen.d.ts.map +1 -0
- package/lib/types/ink/screen.js +1295 -0
- package/lib/types/ink/searchHighlight.d.ts +25 -0
- package/lib/types/ink/searchHighlight.d.ts.map +1 -0
- package/lib/types/ink/searchHighlight.js +82 -0
- package/lib/types/ink/selection.d.ts +323 -0
- package/lib/types/ink/selection.d.ts.map +1 -0
- package/lib/types/ink/selection.js +881 -0
- package/lib/types/ink/squash-text-nodes.d.ts +30 -0
- package/lib/types/ink/squash-text-nodes.d.ts.map +1 -0
- package/lib/types/ink/squash-text-nodes.js +63 -0
- package/lib/types/ink/stringWidth.d.ts +11 -0
- package/lib/types/ink/stringWidth.d.ts.map +1 -0
- package/lib/types/ink/stringWidth.js +209 -0
- package/lib/types/ink/styles.d.ts +324 -0
- package/lib/types/ink/styles.d.ts.map +1 -0
- package/lib/types/ink/styles.js +307 -0
- package/lib/types/ink/supports-hyperlinks.d.ts +20 -0
- package/lib/types/ink/supports-hyperlinks.d.ts.map +1 -0
- package/lib/types/ink/supports-hyperlinks.js +44 -0
- package/lib/types/ink/tabstops.d.ts +9 -0
- package/lib/types/ink/tabstops.d.ts.map +1 -0
- package/lib/types/ink/tabstops.js +46 -0
- package/lib/types/ink/terminal-focus-state.d.ts +35 -0
- package/lib/types/ink/terminal-focus-state.d.ts.map +1 -0
- package/lib/types/ink/terminal-focus-state.js +57 -0
- package/lib/types/ink/terminal-querier.d.ts +161 -0
- package/lib/types/ink/terminal-querier.d.ts.map +1 -0
- package/lib/types/ink/terminal-querier.js +204 -0
- package/lib/types/ink/terminal.d.ts +80 -0
- package/lib/types/ink/terminal.d.ts.map +1 -0
- package/lib/types/ink/terminal.js +268 -0
- package/lib/types/ink/termio/ansi.d.ts +75 -0
- package/lib/types/ink/termio/ansi.d.ts.map +1 -0
- package/lib/types/ink/termio/ansi.js +79 -0
- package/lib/types/ink/termio/csi.d.ts +253 -0
- package/lib/types/ink/termio/csi.d.ts.map +1 -0
- package/lib/types/ink/termio/csi.js +347 -0
- package/lib/types/ink/termio/dec.d.ts +61 -0
- package/lib/types/ink/termio/dec.d.ts.map +1 -0
- package/lib/types/ink/termio/dec.js +76 -0
- package/lib/types/ink/termio/esc.d.ts +15 -0
- package/lib/types/ink/termio/esc.d.ts.map +1 -0
- package/lib/types/ink/termio/esc.js +57 -0
- package/lib/types/ink/termio/osc.d.ts +208 -0
- package/lib/types/ink/termio/osc.d.ts.map +1 -0
- package/lib/types/ink/termio/osc.js +466 -0
- package/lib/types/ink/termio/parser.d.ts +46 -0
- package/lib/types/ink/termio/parser.d.ts.map +1 -0
- package/lib/types/ink/termio/parser.js +366 -0
- package/lib/types/ink/termio/sgr.d.ts +17 -0
- package/lib/types/ink/termio/sgr.d.ts.map +1 -0
- package/lib/types/ink/termio/sgr.js +300 -0
- package/lib/types/ink/termio/tokenize.d.ts +50 -0
- package/lib/types/ink/termio/tokenize.d.ts.map +1 -0
- package/lib/types/ink/termio/tokenize.js +266 -0
- package/lib/types/ink/termio/types.d.ts +211 -0
- package/lib/types/ink/termio/types.d.ts.map +1 -0
- package/lib/types/ink/termio/types.js +68 -0
- package/lib/types/ink/termio.d.ts +25 -0
- package/lib/types/ink/termio.d.ts.map +1 -0
- package/lib/types/ink/termio.js +24 -0
- package/lib/types/ink/truncateToWidth.d.ts +7 -0
- package/lib/types/ink/truncateToWidth.d.ts.map +1 -0
- package/lib/types/ink/truncateToWidth.js +18 -0
- package/lib/types/ink/useTerminalNotification.d.ts +45 -0
- package/lib/types/ink/useTerminalNotification.d.ts.map +1 -0
- package/lib/types/ink/useTerminalNotification.js +70 -0
- package/lib/types/ink/warn.d.ts +7 -0
- package/lib/types/ink/warn.d.ts.map +1 -0
- package/lib/types/ink/warn.js +15 -0
- package/lib/types/ink/widest-line.d.ts +7 -0
- package/lib/types/ink/widest-line.d.ts.map +1 -0
- package/lib/types/ink/widest-line.js +19 -0
- package/lib/types/ink/wrap-text.d.ts +10 -0
- package/lib/types/ink/wrap-text.d.ts.map +1 -0
- package/lib/types/ink/wrap-text.js +61 -0
- package/lib/types/ink/wrapAnsi.d.ts +18 -0
- package/lib/types/ink/wrapAnsi.d.ts.map +1 -0
- package/lib/types/ink/wrapAnsi.js +16 -0
- package/lib/types/invariant.d.ts +20 -0
- package/lib/types/invariant.d.ts.map +1 -0
- package/lib/types/invariant.js +26 -0
- package/lib/types/modelPrefs.d.ts +37 -0
- package/lib/types/modelPrefs.d.ts.map +1 -0
- package/lib/types/modelPrefs.js +66 -0
- package/lib/types/modelRoute.d.ts +79 -0
- package/lib/types/modelRoute.d.ts.map +1 -0
- package/lib/types/modelRoute.js +96 -0
- package/lib/types/native-ts/yoga-layout/enums.d.ts +183 -0
- package/lib/types/native-ts/yoga-layout/enums.d.ts.map +1 -0
- package/lib/types/native-ts/yoga-layout/enums.js +150 -0
- package/lib/types/native-ts/yoga-layout/index.d.ts +788 -0
- package/lib/types/native-ts/yoga-layout/index.d.ts.map +1 -0
- package/lib/types/native-ts/yoga-layout/index.js +2571 -0
- package/lib/types/packaged-skills.d.ts +10 -0
- package/lib/types/packaged-skills.d.ts.map +1 -0
- package/lib/types/packaged-skills.js +63 -0
- package/lib/types/plugin.d.ts +33 -0
- package/lib/types/plugin.d.ts.map +1 -0
- package/lib/types/plugin.js +454 -0
- package/lib/types/presetPrefs.d.ts +29 -0
- package/lib/types/presetPrefs.d.ts.map +1 -0
- package/lib/types/presetPrefs.js +61 -0
- package/lib/types/presets.d.ts +110 -0
- package/lib/types/presets.d.ts.map +1 -0
- package/lib/types/presets.js +114 -0
- package/lib/types/questions.d.ts +97 -0
- package/lib/types/questions.d.ts.map +1 -0
- package/lib/types/questions.js +201 -0
- package/lib/types/screens/Chat.d.ts +23 -0
- package/lib/types/screens/Chat.d.ts.map +1 -0
- package/lib/types/screens/Chat.js +1263 -0
- package/lib/types/screens/StatusLine.d.ts +16 -0
- package/lib/types/screens/StatusLine.d.ts.map +1 -0
- package/lib/types/screens/StatusLine.js +105 -0
- package/lib/types/screens/StatusMetrics.d.ts +87 -0
- package/lib/types/screens/StatusMetrics.d.ts.map +1 -0
- package/lib/types/screens/StatusMetrics.js +252 -0
- package/lib/types/sessionHistory.d.ts +32 -0
- package/lib/types/sessionHistory.d.ts.map +1 -0
- package/lib/types/sessionHistory.js +87 -0
- package/lib/types/theme.d.ts +115 -0
- package/lib/types/theme.d.ts.map +1 -0
- package/lib/types/theme.js +300 -0
- package/lib/types/themePrefs.d.ts +28 -0
- package/lib/types/themePrefs.d.ts.map +1 -0
- package/lib/types/themePrefs.js +59 -0
- package/lib/types/ui.d.ts +28 -0
- package/lib/types/ui.d.ts.map +1 -0
- package/lib/types/ui.js +25 -0
- package/lib/types/update.d.ts +76 -0
- package/lib/types/update.d.ts.map +1 -0
- package/lib/types/update.js +203 -0
- package/lib/types/utils/clipboard.d.ts +43 -0
- package/lib/types/utils/clipboard.d.ts.map +1 -0
- package/lib/types/utils/clipboard.js +82 -0
- package/lib/types/utils/debug.d.ts +8 -0
- package/lib/types/utils/debug.d.ts.map +1 -0
- package/lib/types/utils/debug.js +12 -0
- package/lib/types/utils/earlyInput.d.ts +7 -0
- package/lib/types/utils/earlyInput.d.ts.map +1 -0
- package/lib/types/utils/earlyInput.js +6 -0
- package/lib/types/utils/env.d.ts +10 -0
- package/lib/types/utils/env.d.ts.map +1 -0
- package/lib/types/utils/env.js +9 -0
- package/lib/types/utils/envUtils.d.ts +8 -0
- package/lib/types/utils/envUtils.d.ts.map +1 -0
- package/lib/types/utils/envUtils.js +13 -0
- package/lib/types/utils/execFileNoThrow.d.ts +34 -0
- package/lib/types/utils/execFileNoThrow.d.ts.map +1 -0
- package/lib/types/utils/execFileNoThrow.js +31 -0
- package/lib/types/utils/fullscreen.d.ts +8 -0
- package/lib/types/utils/fullscreen.d.ts.map +1 -0
- package/lib/types/utils/fullscreen.js +10 -0
- package/lib/types/utils/intl.d.ts +7 -0
- package/lib/types/utils/intl.d.ts.map +1 -0
- package/lib/types/utils/intl.js +9 -0
- package/lib/types/utils/loaded-context.d.ts +20 -0
- package/lib/types/utils/loaded-context.d.ts.map +1 -0
- package/lib/types/utils/loaded-context.js +36 -0
- package/lib/types/utils/log.d.ts +7 -0
- package/lib/types/utils/log.d.ts.map +1 -0
- package/lib/types/utils/log.js +9 -0
- package/lib/types/utils/mentions.d.ts +30 -0
- package/lib/types/utils/mentions.d.ts.map +1 -0
- package/lib/types/utils/mentions.js +72 -0
- package/lib/types/utils/narration.d.ts +11 -0
- package/lib/types/utils/narration.d.ts.map +1 -0
- package/lib/types/utils/narration.js +16 -0
- package/lib/types/utils/semver.d.ts +9 -0
- package/lib/types/utils/semver.d.ts.map +1 -0
- package/lib/types/utils/semver.js +24 -0
- package/lib/types/utils/sliceAnsi.d.ts +12 -0
- package/lib/types/utils/sliceAnsi.d.ts.map +1 -0
- package/lib/types/utils/sliceAnsi.js +79 -0
- package/lib/types/working-activity.d.ts +19 -0
- package/lib/types/working-activity.d.ts.map +1 -0
- package/lib/types/working-activity.js +18 -0
- package/package.json +141 -0
- package/skills/audit/SKILL.md +24 -0
- package/skills/bug/SKILL.md +27 -0
- package/skills/pr-comments/SKILL.md +20 -0
- package/skills/practice/SKILL.md +22 -0
- package/skills/release-notes/SKILL.md +25 -0
- package/skills/review/SKILL.md +25 -0
- package/skills/vuln-check/SKILL.md +25 -0
|
@@ -0,0 +1,1263 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { t, getLang, setLang, isLang, writeLangPref, subscribeLang } from '../i18n.js';
|
|
4
|
+
import { Box, Text, useInput, ScrollBox, useTheme } from '../ui.js';
|
|
5
|
+
import { POINTER } from '../cc/figures.js';
|
|
6
|
+
import { formatTokens } from '../cc/format.js';
|
|
7
|
+
import { AskUserQuestionPanel } from '../components/questions/AskUserQuestionPanel.js';
|
|
8
|
+
import { useSearchHighlight } from '../ink/hooks/use-search-highlight.js';
|
|
9
|
+
import { useTerminalTitle } from '../ink/hooks/use-terminal-title.js';
|
|
10
|
+
import { useTerminalFocus } from '../ink/hooks/use-terminal-focus.js';
|
|
11
|
+
import { useCopyOnSelect } from '../ink/hooks/use-copy-on-select.js';
|
|
12
|
+
import { useSelection } from '../ink/hooks/use-selection.js';
|
|
13
|
+
import { NoSelect } from '../ink/components/NoSelect.js';
|
|
14
|
+
import instances from '../ink/instances.js';
|
|
15
|
+
import { LogoHeader, MessageList } from '../components/MessageList.js';
|
|
16
|
+
import { PromptInput } from '../components/PromptInput.js';
|
|
17
|
+
import { GoalTodoPanel } from '../components/GoalTodoPanel.js';
|
|
18
|
+
import { LoadedContextPanel } from '../components/LoadedContextPanel.js';
|
|
19
|
+
import { StatusLine } from './StatusLine.js';
|
|
20
|
+
import { WorkingSpinner, useThinkingStatus } from '../components/WorkingSpinner.js';
|
|
21
|
+
import { ActivityLine, contextPressurePct } from '../components/ActivityLine.js';
|
|
22
|
+
import { ModelPicker } from '../components/ModelPicker.js';
|
|
23
|
+
import { ResumePicker } from '../components/ResumePicker.js';
|
|
24
|
+
import { ActivityPicker } from '../components/ActivityPicker.js';
|
|
25
|
+
import { PresetPicker } from '../components/PresetPicker.js';
|
|
26
|
+
import { ThemePicker, getThemeOptions } from '../components/ThemePicker.js';
|
|
27
|
+
import { FRAME_PRESETS, PRESET_NAMES } from '../components/activityFrames.js';
|
|
28
|
+
import { ThinkingToggle } from '../components/ThinkingToggle.js';
|
|
29
|
+
import { HistorySearchDialog } from '../components/HistorySearchDialog.js';
|
|
30
|
+
import { RewindPicker } from '../components/RewindPicker.js';
|
|
31
|
+
import { LoadingState } from '../components/design-system/LoadingState.js';
|
|
32
|
+
import { Pane } from '../components/design-system/Pane.js';
|
|
33
|
+
import { loadHistory } from '../history.js';
|
|
34
|
+
/** Row kinds the message-selection cursor can land on. */
|
|
35
|
+
const SELECTABLE_KINDS = new Set([
|
|
36
|
+
'user',
|
|
37
|
+
'assistant',
|
|
38
|
+
'tool',
|
|
39
|
+
'reasoning',
|
|
40
|
+
'interrupt',
|
|
41
|
+
'local',
|
|
42
|
+
'local-output',
|
|
43
|
+
'compact',
|
|
44
|
+
]);
|
|
45
|
+
/** Shared empty list for mode-gated derived rows (stable reference, so
|
|
46
|
+
* downstream consumers never see a changing prop when the mode is off). */
|
|
47
|
+
const NO_ROWS = [];
|
|
48
|
+
/** `max` → `Max` (effort levels arrive lower-case from the adapter). */
|
|
49
|
+
function capitalize(text) {
|
|
50
|
+
return text.length === 0 ? text : text[0].toUpperCase() + text.slice(1);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* CC's built-in skill commands, driven through the DSH skill system: each
|
|
54
|
+
* submits an activation prompt the model resolves via its skill catalog/load
|
|
55
|
+
* tools (the corresponding SKILL.md ships under ~/.dsh/skills with dsh-tui).
|
|
56
|
+
*/
|
|
57
|
+
// i18n keys, not resolved strings: module scope evaluates before apply()'s
|
|
58
|
+
// setLang, so t() must run at the call site to follow the active language.
|
|
59
|
+
const SKILL_PROMPTS = {
|
|
60
|
+
audit: 'skill-audit-prompt',
|
|
61
|
+
bug: 'skill-bug-prompt',
|
|
62
|
+
practice: 'skill-practice-prompt',
|
|
63
|
+
review: 'skill-review-prompt',
|
|
64
|
+
pr_comments: 'skill-pr-comments-prompt',
|
|
65
|
+
'release-notes': 'skill-release-notes-prompt',
|
|
66
|
+
'vuln-check': 'skill-vuln-check-prompt',
|
|
67
|
+
};
|
|
68
|
+
/** Terminal-title spinner frames (CC's TITLE_ANIMATION_FRAMES). */
|
|
69
|
+
const TITLE_ANIMATION_FRAMES = ['⠂', '⠐'];
|
|
70
|
+
/** Searchable transcript text for one row (`/` incsearch, CC semantics:
|
|
71
|
+
* user text, assistant text, thinking, tool args/results, local output). */
|
|
72
|
+
function searchableText(row) {
|
|
73
|
+
switch (row.kind) {
|
|
74
|
+
case 'tool':
|
|
75
|
+
return row.tool
|
|
76
|
+
? `${row.tool.name} ${row.tool.argsText} ${row.tool.resultText ?? ''} ${row.tool.errorText ?? ''}`
|
|
77
|
+
: '';
|
|
78
|
+
default:
|
|
79
|
+
return row.text;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Main chat screen in the Claude Code layout: a scrollable transcript
|
|
84
|
+
* (with the current turn's prompt pinned above the viewport while scrolled
|
|
85
|
+
* up), transient notifications, the working spinner, the bordered prompt
|
|
86
|
+
* input (with slash-command overlay) and the status line pinned at the
|
|
87
|
+
* bottom.
|
|
88
|
+
*
|
|
89
|
+
* Ctrl+O toggles expanded detail globally; Shift+↑ enters message-selection
|
|
90
|
+
* mode (↑/↓ move, Enter expands the selected row, Esc exits); Ctrl+C
|
|
91
|
+
* interrupts the running turn, or (when idle) asks for a second Ctrl+C to
|
|
92
|
+
* exit; Enter while scrolled up jumps back to the bottom.
|
|
93
|
+
*/
|
|
94
|
+
export function Chat({ channel, questionStore, onExit, onUpdate, }) {
|
|
95
|
+
// Re-render whenever the channel mutates; rows/status are read fresh below.
|
|
96
|
+
React.useSyncExternalStore(channel.subscribe, () => channel.version);
|
|
97
|
+
// Re-render on language switches so the whole UI hot-swaps its strings.
|
|
98
|
+
React.useSyncExternalStore(subscribeLang, getLang);
|
|
99
|
+
// The pending ask-user-question (DSH user-interaction seam): the model's
|
|
100
|
+
// `ask_user_question` tool parks here until the panel is answered.
|
|
101
|
+
const questionSnapshot = React.useSyncExternalStore(listener => questionStore.subscribe(listener), () => questionStore.getSnapshot());
|
|
102
|
+
// When a questionnaire batch completes, fold a Q&A summary into the
|
|
103
|
+
// transcript (the tool card itself is hidden from the message list).
|
|
104
|
+
const questionOpenRef = React.useRef(questionSnapshot !== null);
|
|
105
|
+
React.useEffect(() => {
|
|
106
|
+
const wasOpen = questionOpenRef.current;
|
|
107
|
+
questionOpenRef.current = questionSnapshot !== null;
|
|
108
|
+
if (wasOpen && questionSnapshot === null) {
|
|
109
|
+
for (const summary of questionStore.takeSummaries()) {
|
|
110
|
+
channel.pushLocal(summary.title, summary.lines);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}, [channel, questionSnapshot, questionStore]);
|
|
114
|
+
const [expanded, setExpanded] = React.useState(false);
|
|
115
|
+
const [helpOpen, setHelpOpen] = React.useState(false);
|
|
116
|
+
const [handle, setHandle] = React.useState(null);
|
|
117
|
+
const [selectionActive, setSelectionActive] = React.useState(false);
|
|
118
|
+
const [selectedId, setSelectedId] = React.useState(null);
|
|
119
|
+
const [expandedRows, setExpandedRows] = React.useState(() => new Set());
|
|
120
|
+
const [modelPickerOpen, setModelPickerOpen] = React.useState(false);
|
|
121
|
+
const [models, setModels] = React.useState([]);
|
|
122
|
+
const [modelIndex, setModelIndex] = React.useState(0);
|
|
123
|
+
const [resumePickerOpen, setResumePickerOpen] = React.useState(false);
|
|
124
|
+
const [resumeSessions, setResumeSessions] = React.useState([]);
|
|
125
|
+
const [resumeIndex, setResumeIndex] = React.useState(0);
|
|
126
|
+
/** `/activity` indicator picker (pi extension's interactive select). */
|
|
127
|
+
const [activityPickerOpen, setActivityPickerOpen] = React.useState(false);
|
|
128
|
+
const [activityIndex, setActivityIndex] = React.useState(0);
|
|
129
|
+
/** `/preset` agent-preset picker (issue #8): roster list loads async. */
|
|
130
|
+
const [presetPickerOpen, setPresetPickerOpen] = React.useState(false);
|
|
131
|
+
const [presetOptions, setPresetOptions] = React.useState([]);
|
|
132
|
+
const [presetIndex, setPresetIndex] = React.useState(0);
|
|
133
|
+
/** `/theme` color-theme picker (built-ins + ~/.dsh-cc/themes user themes). */
|
|
134
|
+
const [themePickerOpen, setThemePickerOpen] = React.useState(false);
|
|
135
|
+
const [themeIndex, setThemeIndex] = React.useState(0);
|
|
136
|
+
const [themeName, setTheme] = useTheme();
|
|
137
|
+
const [showAllMessages, setShowAllMessages] = React.useState(false);
|
|
138
|
+
const [thinkingVisible, setThinkingVisible] = React.useState(true);
|
|
139
|
+
const [thinkingOpen, setThinkingOpen] = React.useState(false);
|
|
140
|
+
const [thinkingFocus, setThinkingFocus] = React.useState(0);
|
|
141
|
+
/** Mid-conversation toggle waiting for Enter confirmation (CC semantics). */
|
|
142
|
+
const [thinkingConfirm, setThinkingConfirm] = React.useState(null);
|
|
143
|
+
/** ctrl+r history search dialog (ported from CC's HistorySearchDialog). */
|
|
144
|
+
const [historyOpen, setHistoryOpen] = React.useState(false);
|
|
145
|
+
const [historyQuery, setHistoryQuery] = React.useState('');
|
|
146
|
+
const [historyCursor, setHistoryCursor] = React.useState(0);
|
|
147
|
+
const [historyFocus, setHistoryFocus] = React.useState(0);
|
|
148
|
+
const [historyEntries, setHistoryEntries] = React.useState([]);
|
|
149
|
+
const [historyFill, setHistoryFill] = React.useState(null);
|
|
150
|
+
/** Double-Esc rewind picker (CC rewind): open state + focused row + confirm. */
|
|
151
|
+
const [rewindOpen, setRewindOpen] = React.useState(false);
|
|
152
|
+
const [rewindIndex, setRewindIndex] = React.useState(0);
|
|
153
|
+
const [rewindConfirm, setRewindConfirm] = React.useState(null);
|
|
154
|
+
/** Startup context panel: expanded by header click or Ctrl+T. */
|
|
155
|
+
const [loadedContextOpen, setLoadedContextOpen] = React.useState(false);
|
|
156
|
+
/** `/` transcript search (less-style incsearch, ported from CC's REPL). */
|
|
157
|
+
const [searchOpen, setSearchOpen] = React.useState(false);
|
|
158
|
+
const [searchQuery, setSearchQuery] = React.useState('');
|
|
159
|
+
const [searchCursor, setSearchCursor] = React.useState(0);
|
|
160
|
+
const [searchCount, setSearchCount] = React.useState(0);
|
|
161
|
+
const [searchCurrent, setSearchCurrent] = React.useState(0);
|
|
162
|
+
const searchAnchorRef = React.useRef(0);
|
|
163
|
+
const rowRefsRef = React.useRef(new Map());
|
|
164
|
+
const { setQuery: setHighlight } = useSearchHighlight();
|
|
165
|
+
// Sticky (pinned-to-bottom) scroll state, subscribed imperatively so
|
|
166
|
+
// wheel events don't re-render React — only the header/pill flip.
|
|
167
|
+
const isSticky = React.useSyncExternalStore(cb => (handle ? handle.subscribe(cb) : () => { }), () => (handle ? handle.isSticky() : true));
|
|
168
|
+
// "N new messages" pill: new rows whose top edge is still BELOW the
|
|
169
|
+
// viewport bottom. The count decrements as the user scrolls down through
|
|
170
|
+
// them and hits 0 (pill hides) once every new row has been on screen —
|
|
171
|
+
// no need to wait for the exact-bottom sticky restore. Chat anchors the
|
|
172
|
+
// "seen up to" point by ROW ID (stable across loadOlder prepends, unlike
|
|
173
|
+
// a rows.length index); MessageList owns the row offsets, so it computes
|
|
174
|
+
// how many rows past that anchor lie below the viewport and reports it.
|
|
175
|
+
const lastSeenRowIdRef = React.useRef(null);
|
|
176
|
+
const [unseenCount, setUnseenCount] = React.useState(0);
|
|
177
|
+
React.useEffect(() => {
|
|
178
|
+
if (isSticky) {
|
|
179
|
+
lastSeenRowIdRef.current = null;
|
|
180
|
+
setUnseenCount(0);
|
|
181
|
+
}
|
|
182
|
+
else if (lastSeenRowIdRef.current === null) {
|
|
183
|
+
lastSeenRowIdRef.current = channel.rows.length
|
|
184
|
+
? channel.rows[channel.rows.length - 1].id
|
|
185
|
+
: -1;
|
|
186
|
+
}
|
|
187
|
+
}, [isSticky, channel.rows]);
|
|
188
|
+
const showPill = !isSticky && unseenCount > 0;
|
|
189
|
+
// Idle Ctrl+C: first press arms an exit, second press exits (CC's
|
|
190
|
+
// double-press semantics, simplified). Under Windows ConPTY the key
|
|
191
|
+
// arrives as stdin data (key.ctrl && input === 'c') — the useInput
|
|
192
|
+
// branch below is the only path; SIGINT is not emitted.
|
|
193
|
+
const exitPendingRef = React.useRef(false);
|
|
194
|
+
const exitTimerRef = React.useRef(null);
|
|
195
|
+
// Live view into the prompt's text for the Ctrl+C rule (clears text when
|
|
196
|
+
// non-empty; the double-press exit only arms on an empty input).
|
|
197
|
+
const promptControllerRef = React.useRef(null);
|
|
198
|
+
const requestExit = () => {
|
|
199
|
+
if (exitPendingRef.current) {
|
|
200
|
+
onExit();
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
exitPendingRef.current = true;
|
|
204
|
+
channel.notify('Press Ctrl+C again to exit');
|
|
205
|
+
exitTimerRef.current = setTimeout(() => {
|
|
206
|
+
exitPendingRef.current = false;
|
|
207
|
+
}, 3000);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
React.useEffect(() => {
|
|
211
|
+
return () => {
|
|
212
|
+
if (exitTimerRef.current)
|
|
213
|
+
clearTimeout(exitTimerRef.current);
|
|
214
|
+
};
|
|
215
|
+
}, []);
|
|
216
|
+
// Spinner timing refs, fed from channel state each render (the spinner
|
|
217
|
+
// only mounts while working, so values are stable for the mount).
|
|
218
|
+
const responseLengthRef = React.useRef(0);
|
|
219
|
+
const loadingStartTimeRef = React.useRef(0);
|
|
220
|
+
const totalPausedMsRef = React.useRef(0);
|
|
221
|
+
const pauseStartTimeRef = React.useRef(null);
|
|
222
|
+
responseLengthRef.current = channel.responseChars;
|
|
223
|
+
loadingStartTimeRef.current = channel.turnStart;
|
|
224
|
+
const thinkingStatus = useThinkingStatus(channel.spinnerMode === 'thinking');
|
|
225
|
+
// Terminal tab title (ported from CC's AnimatedTerminalTitle): the session
|
|
226
|
+
// title when set, else "dsh-TUI"; a `⠂/⠐` spinner prefix while a turn is
|
|
227
|
+
// working (960ms cadence, only while the terminal is focused), a static
|
|
228
|
+
// `✦` otherwise. dsh-TUI brands the idle prefix with the DeepSeek whale.
|
|
229
|
+
const [titleFrame, setTitleFrame] = React.useState(0);
|
|
230
|
+
const terminalFocused = useTerminalFocus();
|
|
231
|
+
// Mouse text selection auto-copy (CC's copy-on-select): active only in
|
|
232
|
+
// fullscreen (<AlternateScreen> supplies mouse tracking); a no-op
|
|
233
|
+
// subscription in inline mode, where selection belongs to the terminal.
|
|
234
|
+
// The copy clears the highlight and posts a transient notification.
|
|
235
|
+
useCopyOnSelect(text => channel.notify(t('copied-chars', { n: text.length }), { timeoutMs: 1500 }));
|
|
236
|
+
const { clearSelection: clearMouseSelection, hasSelection: hasMouseSelection } = useSelection();
|
|
237
|
+
React.useEffect(() => {
|
|
238
|
+
if (!channel.working || !terminalFocused)
|
|
239
|
+
return;
|
|
240
|
+
const interval = setInterval(() => {
|
|
241
|
+
setTitleFrame(f => (f + 1) % TITLE_ANIMATION_FRAMES.length);
|
|
242
|
+
}, 960);
|
|
243
|
+
return () => { clearInterval(interval); };
|
|
244
|
+
}, [channel.working, terminalFocused]);
|
|
245
|
+
const titlePrefix = channel.working
|
|
246
|
+
? (TITLE_ANIMATION_FRAMES[titleFrame] ?? '✦')
|
|
247
|
+
: '✦';
|
|
248
|
+
useTerminalTitle(`${titlePrefix} 🐋 ${channel.sessionTitle}`);
|
|
249
|
+
/**
|
|
250
|
+
* Dispatch a slash command; false lets the input flow to the model.
|
|
251
|
+
* Built-in names run the local switch; anything registered by a DSH
|
|
252
|
+
* plugin (plan/goal/…) dispatches through the command registry, whose
|
|
253
|
+
* result text lands as a notification. `rawInput` carries the text after
|
|
254
|
+
* the command name (`/plan off` → ` off`).
|
|
255
|
+
*/
|
|
256
|
+
const runCommand = (name, rawInput = '') => {
|
|
257
|
+
switch (name) {
|
|
258
|
+
case 'activity': {
|
|
259
|
+
// Ported from the pi working-activity extension: bare `/activity`
|
|
260
|
+
// opens the interactive indicator picker; `/activity frames <name>`
|
|
261
|
+
// switches directly; `/activity frames` lists presets; `/activity
|
|
262
|
+
// status` shows the current choice. The choice persists to
|
|
263
|
+
// ~/.dsh-cc/working-activity.json and survives restarts.
|
|
264
|
+
const parts = rawInput.trim().split(/\s+/).filter(Boolean);
|
|
265
|
+
if (parts[0] === 'status') {
|
|
266
|
+
setHelpOpen(false);
|
|
267
|
+
channel.pushLocal('/activity', [
|
|
268
|
+
t('activity-current-preset', { name: channel.activityFrames ?? 'claude' }),
|
|
269
|
+
t('activity-switch-hint'),
|
|
270
|
+
t('activity-persist-hint'),
|
|
271
|
+
]);
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
if (parts[0] === 'frames') {
|
|
275
|
+
setHelpOpen(false);
|
|
276
|
+
if (parts[1]) {
|
|
277
|
+
channel.setActivityFrames(parts[1].toLowerCase());
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
const current = channel.activityFrames;
|
|
281
|
+
channel.pushLocal('/activity', [
|
|
282
|
+
t('activity-current-direct', { name: current ?? 'claude' }),
|
|
283
|
+
...PRESET_NAMES.map(name => `${name.padEnd(10)} ${name === 'random' ? t('activity-random-each') : FRAME_PRESETS[name].frames.slice(0, 5).join(' ')}${name === current ? t('activity-current-marker') : ''}`),
|
|
284
|
+
]);
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
if (parts.length > 0) {
|
|
288
|
+
channel.notify(t('activity-usage'), { color: 'warning' });
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
setHelpOpen(false);
|
|
292
|
+
setActivityIndex(Math.max(0, PRESET_NAMES.indexOf(channel.activityFrames ?? 'random')));
|
|
293
|
+
setActivityPickerOpen(true);
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
case 'preset': {
|
|
297
|
+
// issue #8: bare `/preset` opens the roster picker (standard/code/
|
|
298
|
+
// minimal/cordis plus any user-authored presets); `/preset <id>`
|
|
299
|
+
// switches directly; `/preset status` shows the current choice. A
|
|
300
|
+
// blank session swaps composition in place (official blank-only
|
|
301
|
+
// rule); a started session is locked and the choice persists as the
|
|
302
|
+
// default for future sessions (~/.dsh-cc/agent-preset.json).
|
|
303
|
+
const parts = rawInput.trim().split(/\s+/).filter(Boolean);
|
|
304
|
+
if (parts[0] === 'status') {
|
|
305
|
+
setHelpOpen(false);
|
|
306
|
+
channel.pushLocal('/preset', [
|
|
307
|
+
t('preset-current', { name: channel.agentPreset ?? t('preset-roster-missing') }),
|
|
308
|
+
t('preset-switch-hint'),
|
|
309
|
+
t('preset-persist-hint'),
|
|
310
|
+
t('preset-lock-hint'),
|
|
311
|
+
]);
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
if (parts.length > 0) {
|
|
315
|
+
setHelpOpen(false);
|
|
316
|
+
void channel.switchPreset(parts[0]);
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
setHelpOpen(false);
|
|
320
|
+
setPresetPickerOpen(true);
|
|
321
|
+
void channel.listPresets().then((list) => {
|
|
322
|
+
if (list.length === 0) {
|
|
323
|
+
setPresetPickerOpen(false);
|
|
324
|
+
channel.notify(t('preset-roster-unmounted'), { color: 'warning' });
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
setPresetOptions(list);
|
|
328
|
+
const index = list.findIndex(preset => preset.id === channel.agentPreset);
|
|
329
|
+
setPresetIndex(index >= 0 ? index : 0);
|
|
330
|
+
});
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
case 'lang': {
|
|
334
|
+
// `/lang` shows the current UI language, `/lang en|zh` switches
|
|
335
|
+
// (hot-swap, persisted to ~/.dsh-cc/lang.json). Precedence on next
|
|
336
|
+
// launch: CC_TUI_LANG > cordis.yml `lang` > the persisted choice.
|
|
337
|
+
const parts = rawInput.trim().split(/\s+/).filter(Boolean);
|
|
338
|
+
if (parts[0] === 'status') {
|
|
339
|
+
setHelpOpen(false);
|
|
340
|
+
channel.pushLocal('/lang', [
|
|
341
|
+
t('lang-current', { lang: getLang() }),
|
|
342
|
+
t('lang-switch-hint'),
|
|
343
|
+
t('lang-persist-hint'),
|
|
344
|
+
]);
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
if (parts.length > 0) {
|
|
348
|
+
setHelpOpen(false);
|
|
349
|
+
if (isLang(parts[0])) {
|
|
350
|
+
const ok = writeLangPref(parts[0]);
|
|
351
|
+
setLang(parts[0]);
|
|
352
|
+
channel.notify(ok ? t('lang-switched', { lang: parts[0] }) : t('lang-switch-failed', { lang: parts[0] }), { color: ok ? 'success' : 'error' });
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
channel.notify(t('lang-unknown', { lang: parts[0] }), { color: 'error' });
|
|
356
|
+
}
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
setHelpOpen(false);
|
|
360
|
+
channel.pushLocal('/lang', [
|
|
361
|
+
t('lang-current', { lang: getLang() }),
|
|
362
|
+
t('lang-switch-hint'),
|
|
363
|
+
t('lang-persist-hint'),
|
|
364
|
+
]);
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
case 'theme': {
|
|
368
|
+
// Bare `/theme` opens the interactive color picker (built-in
|
|
369
|
+
// palettes + user themes from ~/.dsh-cc/themes); `/theme <name>`
|
|
370
|
+
// switches directly; `/theme status` shows the current choice.
|
|
371
|
+
// Selection persists to ~/.dsh-cc/theme.json and hot swaps via the
|
|
372
|
+
// ThemeProvider setter (CC_TUI_THEME still wins on next launch).
|
|
373
|
+
const parts = rawInput.trim().split(/\s+/).filter(Boolean);
|
|
374
|
+
if (parts[0] === 'status') {
|
|
375
|
+
setHelpOpen(false);
|
|
376
|
+
channel.pushLocal('/theme', [
|
|
377
|
+
t('theme-current', { name: themeName }),
|
|
378
|
+
t('theme-switch-hint'),
|
|
379
|
+
t('theme-persist-hint'),
|
|
380
|
+
t('theme-custom-hint'),
|
|
381
|
+
]);
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
if (parts.length > 0) {
|
|
385
|
+
setHelpOpen(false);
|
|
386
|
+
const ok = setTheme(parts[0]);
|
|
387
|
+
channel.notify(ok ? t('theme-switched-saved', { name: parts[0] }) : t('theme-unknown', { name: parts[0] }), { color: ok ? 'success' : 'error' });
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
setHelpOpen(false);
|
|
391
|
+
setThemeIndex(Math.max(0, getThemeOptions().findIndex(option => option.value === themeName)));
|
|
392
|
+
setThemePickerOpen(true);
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
case 'new': {
|
|
396
|
+
// One-shot `/new` (issue #25): the old session stays persisted and
|
|
397
|
+
// is recoverable via /resume, so discarding the live view is
|
|
398
|
+
// non-destructive — no CC-style "press /new again" confirmation.
|
|
399
|
+
setHelpOpen(false);
|
|
400
|
+
void channel.newSession().then((ok) => {
|
|
401
|
+
if (ok)
|
|
402
|
+
channel.notify('New session started');
|
|
403
|
+
});
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
case 'clear':
|
|
407
|
+
channel.clear();
|
|
408
|
+
// channel.clear() resets row ids to 0; stale expanded/selection
|
|
409
|
+
// state would mis-highlight fresh rows (known-limitation fix).
|
|
410
|
+
setExpandedRows(new Set());
|
|
411
|
+
setSelectedId(null);
|
|
412
|
+
setSelectionActive(false);
|
|
413
|
+
return true;
|
|
414
|
+
case 'compact':
|
|
415
|
+
channel.compact();
|
|
416
|
+
return true;
|
|
417
|
+
case 'help':
|
|
418
|
+
setHelpOpen(true);
|
|
419
|
+
return true;
|
|
420
|
+
case 'model':
|
|
421
|
+
setHelpOpen(false);
|
|
422
|
+
setModelPickerOpen(true);
|
|
423
|
+
void channel.listModels().then((list) => {
|
|
424
|
+
setModels(list);
|
|
425
|
+
const index = list.findIndex(model => model.provider === channel.provider && model.id === channel.model);
|
|
426
|
+
setModelIndex(index >= 0 ? index : 0);
|
|
427
|
+
});
|
|
428
|
+
return true;
|
|
429
|
+
case 'thinking':
|
|
430
|
+
setHelpOpen(false);
|
|
431
|
+
setThinkingOpen(true);
|
|
432
|
+
setThinkingFocus(thinkingVisible ? 0 : 1);
|
|
433
|
+
return true;
|
|
434
|
+
case 'tokens': {
|
|
435
|
+
const usage = `Tokens: ${formatTokens(channel.tokens.input)} in · ${formatTokens(channel.tokens.output)} out`;
|
|
436
|
+
if (channel.contextWindow === undefined) {
|
|
437
|
+
channel.notify(usage);
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
const percent = Math.max(0, Math.min(100, Math.round((channel.tokens.input / channel.contextWindow) * 100)));
|
|
441
|
+
channel.notify(`${usage} · ${percent}% of context`);
|
|
442
|
+
}
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
case 'resume': {
|
|
446
|
+
setHelpOpen(false);
|
|
447
|
+
void (async () => {
|
|
448
|
+
const sessions = await channel.listSessions();
|
|
449
|
+
// The current session cannot be resumed into itself (agents.resume
|
|
450
|
+
// rejects a live session), so it is excluded from the picker —
|
|
451
|
+
// otherwise the fresh empty session of this launch always tops the
|
|
452
|
+
// list as an unopenable row.
|
|
453
|
+
const pickable = sessions.filter(session => session.id !== channel.agentId);
|
|
454
|
+
setResumeSessions(pickable);
|
|
455
|
+
if (pickable.length === 0) {
|
|
456
|
+
channel.notify(t('resume-none-in-cwd'));
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
setResumePickerOpen(true);
|
|
460
|
+
setResumeIndex(0);
|
|
461
|
+
})();
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
case 'rewind':
|
|
465
|
+
// Same picker as PromptInput's double-Esc on an empty input (CC
|
|
466
|
+
// rewind); `openRewind` notifies when there is nothing to rewind.
|
|
467
|
+
setHelpOpen(false);
|
|
468
|
+
openRewind();
|
|
469
|
+
return true;
|
|
470
|
+
case 'exit':
|
|
471
|
+
onExit();
|
|
472
|
+
return true;
|
|
473
|
+
case 'status': {
|
|
474
|
+
const usage = channel.lastUsage;
|
|
475
|
+
const pct = channel.contextWindow === undefined
|
|
476
|
+
? undefined
|
|
477
|
+
: Math.max(0, Math.min(100, Math.round((channel.tokens.input / channel.contextWindow) * 100)));
|
|
478
|
+
const lines = [
|
|
479
|
+
`${t('status-model', { model: channel.model })}${channel.reasoningEffort ? ` · ${capitalize(channel.reasoningEffort)} effort` : ''}`,
|
|
480
|
+
`${t('status-state', { state: channel.working ? t('status-working') : t('status-idle') })}`,
|
|
481
|
+
`${t('status-session', { id: channel.agentId })}`,
|
|
482
|
+
`${t('status-dir', { cwd: channel.cwd })}${channel.gitBranch ? ` · ${channel.gitBranch}` : ''}`,
|
|
483
|
+
`Tokens ${formatTokens(channel.tokens.input)} in → ${formatTokens(channel.tokens.output)} out`,
|
|
484
|
+
];
|
|
485
|
+
if (usage !== undefined) {
|
|
486
|
+
const total = usage.input + usage.cacheRead + usage.cacheWrite;
|
|
487
|
+
const rate = total > 0 ? ((usage.cacheRead / total) * 100).toFixed(1) : '0.0';
|
|
488
|
+
lines.push(t('cost-cache-rate', { rate, read: formatTokens(usage.cacheRead), write: formatTokens(usage.cacheWrite) }));
|
|
489
|
+
}
|
|
490
|
+
if (pct !== undefined)
|
|
491
|
+
lines.push(t('cost-context', { pct }));
|
|
492
|
+
if (channel.sessionTitle)
|
|
493
|
+
lines.push(t('status-title', { title: channel.sessionTitle }));
|
|
494
|
+
setHelpOpen(false);
|
|
495
|
+
channel.pushLocal('/status', lines);
|
|
496
|
+
return true;
|
|
497
|
+
}
|
|
498
|
+
case 'cost': {
|
|
499
|
+
const usage = channel.lastUsage;
|
|
500
|
+
const lines = [
|
|
501
|
+
`Tokens ${formatTokens(channel.tokens.input)} in → ${formatTokens(channel.tokens.output)} out`,
|
|
502
|
+
];
|
|
503
|
+
if (usage !== undefined) {
|
|
504
|
+
const total = usage.input + usage.cacheRead + usage.cacheWrite;
|
|
505
|
+
const rate = total > 0 ? ((usage.cacheRead / total) * 100).toFixed(1) : '0.0';
|
|
506
|
+
lines.push(t('cost-cache-hit-rate', { rate, read: formatTokens(usage.cacheRead), write: formatTokens(usage.cacheWrite) }));
|
|
507
|
+
}
|
|
508
|
+
lines.push(t('cost-note'));
|
|
509
|
+
setHelpOpen(false);
|
|
510
|
+
channel.pushLocal('/cost', lines);
|
|
511
|
+
return true;
|
|
512
|
+
}
|
|
513
|
+
case 'config': {
|
|
514
|
+
const userHome = process.env.USERPROFILE ?? '';
|
|
515
|
+
const lines = [
|
|
516
|
+
t('doctor-example-config', { path: 'dsh --profile dsh-tui' }),
|
|
517
|
+
t('doctor-user-config', { path: `${userHome}/.dsh/profiles/dsh-tui/cordis.patch.yml` }),
|
|
518
|
+
'',
|
|
519
|
+
t('doctor-launch-hint'),
|
|
520
|
+
t('doctor-route-hint'),
|
|
521
|
+
];
|
|
522
|
+
setHelpOpen(false);
|
|
523
|
+
channel.pushLocal('/config', lines);
|
|
524
|
+
return true;
|
|
525
|
+
}
|
|
526
|
+
case 'doctor':
|
|
527
|
+
setHelpOpen(false);
|
|
528
|
+
channel.pushLocal('/doctor', channel.doctorInfo());
|
|
529
|
+
return true;
|
|
530
|
+
case 'export': {
|
|
531
|
+
const target = channel.exportSession();
|
|
532
|
+
channel.notify(target === null
|
|
533
|
+
? t('export-failed')
|
|
534
|
+
: t('export-saved', { target }), target === null ? { color: 'error', timeoutMs: 8000 } : { timeoutMs: 8000 });
|
|
535
|
+
return true;
|
|
536
|
+
}
|
|
537
|
+
case 'init': {
|
|
538
|
+
const result = channel.initWorkspace();
|
|
539
|
+
if (result === null)
|
|
540
|
+
channel.notify(t('agentsmd-create-failed'), { color: 'error' });
|
|
541
|
+
else if (result === 'exists')
|
|
542
|
+
channel.notify(t('agentsmd-exists'));
|
|
543
|
+
else
|
|
544
|
+
channel.notify(t('agentsmd-created', { result }));
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
547
|
+
case 'agents':
|
|
548
|
+
setHelpOpen(false);
|
|
549
|
+
void channel.listSubagents().then((lines) => {
|
|
550
|
+
channel.pushLocal('/agents', lines);
|
|
551
|
+
});
|
|
552
|
+
return true;
|
|
553
|
+
case 'login': {
|
|
554
|
+
const key = process.env.DEEPSEEK_API_KEY;
|
|
555
|
+
const lines = [
|
|
556
|
+
`${t('login-api-key', { key: key ? key.slice(0, 6) + '…' + key.slice(-4) : t('login-key-missing') })}`,
|
|
557
|
+
`${t('login-base-url', { url: process.env.DEEPSEEK_BASE_URL ?? t('login-official-endpoint') })}`,
|
|
558
|
+
t('login-source-hint'),
|
|
559
|
+
];
|
|
560
|
+
setHelpOpen(false);
|
|
561
|
+
channel.pushLocal('/login', lines);
|
|
562
|
+
return true;
|
|
563
|
+
}
|
|
564
|
+
case 'logout':
|
|
565
|
+
channel.notify(t('login-logout-hint'));
|
|
566
|
+
return true;
|
|
567
|
+
case 'permissions':
|
|
568
|
+
setHelpOpen(false);
|
|
569
|
+
channel.pushLocal('/permissions', [
|
|
570
|
+
t('permissions-policy-hint'),
|
|
571
|
+
t('permissions-approval-hint'),
|
|
572
|
+
]);
|
|
573
|
+
return true;
|
|
574
|
+
case 'add-dir':
|
|
575
|
+
setHelpOpen(false);
|
|
576
|
+
channel.pushLocal('/add-dir', [
|
|
577
|
+
t('permissions-root-hint', { cwd: channel.cwd }),
|
|
578
|
+
t('permissions-path-hint'),
|
|
579
|
+
]);
|
|
580
|
+
return true;
|
|
581
|
+
case 'hooks':
|
|
582
|
+
setHelpOpen(false);
|
|
583
|
+
channel.pushLocal('/hooks', [
|
|
584
|
+
t('hooks-not-mounted'),
|
|
585
|
+
t('hooks-mount-hint'),
|
|
586
|
+
]);
|
|
587
|
+
return true;
|
|
588
|
+
case 'mcp':
|
|
589
|
+
setHelpOpen(false);
|
|
590
|
+
channel.pushLocal('/mcp', channel.mcpStatus());
|
|
591
|
+
return true;
|
|
592
|
+
case 'memory':
|
|
593
|
+
setHelpOpen(false);
|
|
594
|
+
channel.pushLocal('/memory', [
|
|
595
|
+
t('memory-none'),
|
|
596
|
+
t('memory-hint'),
|
|
597
|
+
]);
|
|
598
|
+
return true;
|
|
599
|
+
case 'update':
|
|
600
|
+
setHelpOpen(false);
|
|
601
|
+
if (onUpdate === undefined) {
|
|
602
|
+
channel.notify(t('update-unavailable'), { color: 'warning' });
|
|
603
|
+
}
|
|
604
|
+
else if (channel.working) {
|
|
605
|
+
channel.notify(t('update-working'), { color: 'warning' });
|
|
606
|
+
}
|
|
607
|
+
else {
|
|
608
|
+
channel.notify(t('update-starting'));
|
|
609
|
+
onUpdate();
|
|
610
|
+
}
|
|
611
|
+
return true;
|
|
612
|
+
case 'vim':
|
|
613
|
+
channel.notify(t('vim-not-implemented'));
|
|
614
|
+
return true;
|
|
615
|
+
case 'terminal-setup':
|
|
616
|
+
setHelpOpen(false);
|
|
617
|
+
channel.pushLocal('/terminal-setup', [
|
|
618
|
+
t('terminal-setup-hint'),
|
|
619
|
+
t('terminal-paste-hint'),
|
|
620
|
+
]);
|
|
621
|
+
return true;
|
|
622
|
+
case 'connect':
|
|
623
|
+
setHelpOpen(false);
|
|
624
|
+
channel.pushLocal('/connect', [t('connect-none')]);
|
|
625
|
+
return true;
|
|
626
|
+
case 'audit':
|
|
627
|
+
case 'bug':
|
|
628
|
+
case 'practice':
|
|
629
|
+
case 'review':
|
|
630
|
+
case 'pr_comments':
|
|
631
|
+
case 'release-notes':
|
|
632
|
+
case 'vuln-check': {
|
|
633
|
+
// CC's skill commands: drive the DSH skill system by sending the
|
|
634
|
+
// activation prompt to the model (it loads the skill via its skill
|
|
635
|
+
// catalog/load tools when the SKILL.md ships in ~/.dsh/skills).
|
|
636
|
+
const key = SKILL_PROMPTS[name];
|
|
637
|
+
if (key)
|
|
638
|
+
channel.submit(t(key));
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
default: {
|
|
642
|
+
// Plugin-registered command (DSH command registry): dispatch through
|
|
643
|
+
// the channel, whose execution logs command/run + command/done (the
|
|
644
|
+
// plan-mode projection folds those records, so /plan state stays
|
|
645
|
+
// consistent). Unknown names fall through to the model.
|
|
646
|
+
const external = channel.commandList.find(command => command.external && command.name === name);
|
|
647
|
+
if (external) {
|
|
648
|
+
setHelpOpen(false);
|
|
649
|
+
void channel.runExternalCommand(name, rawInput).then((text) => {
|
|
650
|
+
if (text !== undefined && text !== '') {
|
|
651
|
+
channel.notify(text);
|
|
652
|
+
}
|
|
653
|
+
else if (text === undefined) {
|
|
654
|
+
channel.notify(`/${name}: no such command`, { color: 'error' });
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
return true;
|
|
658
|
+
}
|
|
659
|
+
return false;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
// === Message-selection mode (CC's Shift+↑ message actions) ===
|
|
664
|
+
// NOTE: rows is a live in-place array on the channel (no new reference per
|
|
665
|
+
// update), so derived lists must be computed per render — a useMemo keyed
|
|
666
|
+
// on `channel.rows` would freeze at the first empty snapshot forever.
|
|
667
|
+
// Both lists only feed their respective modes; computing them
|
|
668
|
+
// unconditionally cost an O(rows) scan + array allocation per render
|
|
669
|
+
// (every streamed chunk), so they are gated on the consuming mode.
|
|
670
|
+
const selectableRows = selectionActive
|
|
671
|
+
? channel.rows.filter(row => SELECTABLE_KINDS.has(row.kind))
|
|
672
|
+
: NO_ROWS;
|
|
673
|
+
// ctrl+r history search: substring match on the query, newest first.
|
|
674
|
+
const historyMatches = React.useMemo(() => {
|
|
675
|
+
const q = historyQuery.trim().toLowerCase();
|
|
676
|
+
return q ? historyEntries.filter(e => e.text.toLowerCase().includes(q)) : historyEntries;
|
|
677
|
+
}, [historyEntries, historyQuery]);
|
|
678
|
+
// Double-Esc rewind: the user's own messages, newest first (CC lists the
|
|
679
|
+
// selectable user turns; steering side-questions are excluded). Computed
|
|
680
|
+
// per render while the picker is open — `channel.rows` is a live in-place
|
|
681
|
+
// array (see selectableRows).
|
|
682
|
+
const rewindRows = rewindOpen
|
|
683
|
+
? channel.rows
|
|
684
|
+
.filter(row => row.kind === 'user' && row.label === undefined)
|
|
685
|
+
.reverse()
|
|
686
|
+
: NO_ROWS;
|
|
687
|
+
/** Open the rewind picker (from PromptInput's double-Esc on an empty input). */
|
|
688
|
+
const openRewind = () => {
|
|
689
|
+
// rewindOpen is still false this render, so rewindRows is empty — scan
|
|
690
|
+
// directly instead of reading the gated list.
|
|
691
|
+
const candidates = channel.rows
|
|
692
|
+
.filter(row => row.kind === 'user' && row.label === undefined)
|
|
693
|
+
.reverse();
|
|
694
|
+
if (candidates.length === 0) {
|
|
695
|
+
channel.notify('Nothing to rewind yet');
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
setRewindIndex(0);
|
|
699
|
+
setRewindConfirm(null);
|
|
700
|
+
setRewindOpen(true);
|
|
701
|
+
};
|
|
702
|
+
/** Execute the confirmed rewind; the message comes back into the input. */
|
|
703
|
+
const performRewind = async (row) => {
|
|
704
|
+
const text = await channel.rewindTo(row);
|
|
705
|
+
if (text !== null) {
|
|
706
|
+
// CC puts the restored message back in the prompt for re-editing.
|
|
707
|
+
setHistoryFill(text);
|
|
708
|
+
channel.notify('Rewound — edit and press Enter to resend');
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
// Row seeking under layout virtualization: a mounted row seeks directly;
|
|
712
|
+
// an unmounted one is force-mounted first, then sought by the completion
|
|
713
|
+
// effect below once its ref lands.
|
|
714
|
+
const [forceMountRowId, setForceMountRowId] = React.useState(null);
|
|
715
|
+
const seekRow = (rowId) => {
|
|
716
|
+
const el = rowRefsRef.current.get(rowId);
|
|
717
|
+
if (el) {
|
|
718
|
+
handle?.scrollToElement(el);
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
setForceMountRowId(rowId);
|
|
722
|
+
};
|
|
723
|
+
React.useLayoutEffect(() => {
|
|
724
|
+
if (forceMountRowId === null)
|
|
725
|
+
return;
|
|
726
|
+
const el = rowRefsRef.current.get(forceMountRowId);
|
|
727
|
+
if (el) {
|
|
728
|
+
handle?.scrollToElement(el);
|
|
729
|
+
setForceMountRowId(null);
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
// `/` transcript search: rows whose searchable text contains the query.
|
|
733
|
+
// Computed per render — `channel.rows` is a live in-place array (see
|
|
734
|
+
// selectableRows); a useMemo would freeze the match list at mount.
|
|
735
|
+
const searchMatches = (() => {
|
|
736
|
+
const q = searchQuery.toLowerCase();
|
|
737
|
+
if (!q)
|
|
738
|
+
return [];
|
|
739
|
+
return channel.rows
|
|
740
|
+
.map((row, index) => ({ row, index, text: searchableText(row).toLowerCase() }))
|
|
741
|
+
.filter(m => m.text.includes(q));
|
|
742
|
+
})();
|
|
743
|
+
// Incsearch: highlight all matches (screen-space overlay) and keep the
|
|
744
|
+
// current match row in view as the query changes (CC semantics).
|
|
745
|
+
React.useEffect(() => {
|
|
746
|
+
if (!searchOpen)
|
|
747
|
+
return;
|
|
748
|
+
setHighlight(searchQuery);
|
|
749
|
+
const count = searchMatches.length;
|
|
750
|
+
setSearchCount(count);
|
|
751
|
+
const current = Math.min(searchCurrent, Math.max(0, count - 1));
|
|
752
|
+
setSearchCurrent(current);
|
|
753
|
+
const target = searchMatches[current];
|
|
754
|
+
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty/filtered list
|
|
755
|
+
if (target) {
|
|
756
|
+
seekRow(target.row.id);
|
|
757
|
+
}
|
|
758
|
+
}, [searchQuery, searchOpen]);
|
|
759
|
+
// n/N navigation: move the current match into view.
|
|
760
|
+
React.useEffect(() => {
|
|
761
|
+
if (!searchOpen)
|
|
762
|
+
return;
|
|
763
|
+
const target = searchMatches[searchCurrent];
|
|
764
|
+
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty/filtered list
|
|
765
|
+
if (target) {
|
|
766
|
+
seekRow(target.row.id);
|
|
767
|
+
}
|
|
768
|
+
}, [searchCurrent]);
|
|
769
|
+
const enterSelection = () => {
|
|
770
|
+
setSelectionActive(true);
|
|
771
|
+
const last = selectableRows[selectableRows.length - 1];
|
|
772
|
+
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: empty selectable list
|
|
773
|
+
setSelectedId(last ? last.id : null);
|
|
774
|
+
};
|
|
775
|
+
const moveSelection = (delta) => {
|
|
776
|
+
if (selectedId === null)
|
|
777
|
+
return;
|
|
778
|
+
const index = selectableRows.findIndex(row => row.id === selectedId);
|
|
779
|
+
if (index < 0)
|
|
780
|
+
return;
|
|
781
|
+
const next = selectableRows[index + delta];
|
|
782
|
+
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index
|
|
783
|
+
if (next)
|
|
784
|
+
setSelectedId(next.id);
|
|
785
|
+
};
|
|
786
|
+
// useCallback: these feed MessageList → MemoRow's shallow compare; fresh
|
|
787
|
+
// closures each render would defeat every row's memo.
|
|
788
|
+
const toggleRowExpanded = React.useCallback((rowId) => {
|
|
789
|
+
setExpandedRows((previous) => {
|
|
790
|
+
const next = new Set(previous);
|
|
791
|
+
if (next.has(rowId))
|
|
792
|
+
next.delete(rowId);
|
|
793
|
+
else
|
|
794
|
+
next.add(rowId);
|
|
795
|
+
return next;
|
|
796
|
+
});
|
|
797
|
+
}, []);
|
|
798
|
+
const registerRowRef = React.useCallback((rowId, el) => {
|
|
799
|
+
if (el)
|
|
800
|
+
rowRefsRef.current.set(rowId, el);
|
|
801
|
+
else
|
|
802
|
+
rowRefsRef.current.delete(rowId);
|
|
803
|
+
}, []);
|
|
804
|
+
useInput((input, key, event) => {
|
|
805
|
+
// The questionnaire owns the keyboard while a question is pending (the
|
|
806
|
+
// panel's own useInput handles ↑/↓/Space/Tab/Enter/Esc; the prompt
|
|
807
|
+
// input is unmounted, so nothing else should see these keys).
|
|
808
|
+
if (questionSnapshot !== null)
|
|
809
|
+
return;
|
|
810
|
+
// Mouse wheel scrolls the transcript — in fullscreen there is no
|
|
811
|
+
// terminal scrollback (alt-screen), so this is the only way back.
|
|
812
|
+
// Imperative scrollBy: no React re-render per notch (CC semantics).
|
|
813
|
+
// Events only arrive with mouse tracking on; inline mode never sees
|
|
814
|
+
// them, so this is a no-op there.
|
|
815
|
+
if (key.wheelUp || key.wheelDown) {
|
|
816
|
+
handle?.scrollBy(key.wheelUp ? -3 : 3);
|
|
817
|
+
event.stopImmediatePropagation();
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
// Esc clears a settled mouse selection first (CC precedence), ahead of
|
|
821
|
+
// every other Esc meaning below (close pickers, interrupt the turn).
|
|
822
|
+
// hasSelection() is an imperative read — no subscription needed.
|
|
823
|
+
if (key.escape && hasMouseSelection()) {
|
|
824
|
+
clearMouseSelection();
|
|
825
|
+
event.stopImmediatePropagation();
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
if (searchOpen) {
|
|
829
|
+
// Transcript search bar (less-style): edit the query, Enter commits
|
|
830
|
+
// (query persists for n/N), Esc/ctrl+c cancels back to the anchor.
|
|
831
|
+
if (key.escape || (key.ctrl && input === 'c')) {
|
|
832
|
+
setSearchOpen(false);
|
|
833
|
+
setHighlight('');
|
|
834
|
+
handle?.scrollTo(searchAnchorRef.current);
|
|
835
|
+
}
|
|
836
|
+
else if (key.return) {
|
|
837
|
+
// Enter commits; 0-match junk queries don't persist (CC behavior).
|
|
838
|
+
if (searchCount === 0)
|
|
839
|
+
setSearchQuery('');
|
|
840
|
+
setSearchOpen(false);
|
|
841
|
+
}
|
|
842
|
+
else if (key.backspace) {
|
|
843
|
+
if (searchCursor > 0) {
|
|
844
|
+
setSearchQuery(searchQuery.slice(0, searchCursor - 1) + searchQuery.slice(searchCursor));
|
|
845
|
+
setSearchCursor(searchCursor - 1);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
else if (key.delete) {
|
|
849
|
+
if (searchCursor < searchQuery.length) {
|
|
850
|
+
setSearchQuery(searchQuery.slice(0, searchCursor) + searchQuery.slice(searchCursor + 1));
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
else if (key.leftArrow) {
|
|
854
|
+
setSearchCursor(c => Math.max(0, c - 1));
|
|
855
|
+
}
|
|
856
|
+
else if (key.rightArrow) {
|
|
857
|
+
setSearchCursor(c => Math.min(searchQuery.length, c + 1));
|
|
858
|
+
}
|
|
859
|
+
else if (key.home) {
|
|
860
|
+
setSearchCursor(0);
|
|
861
|
+
}
|
|
862
|
+
else if (key.end) {
|
|
863
|
+
setSearchCursor(searchQuery.length);
|
|
864
|
+
}
|
|
865
|
+
else if (!key.ctrl && !key.meta && input) {
|
|
866
|
+
const next = searchQuery.slice(0, searchCursor) + input + searchQuery.slice(searchCursor);
|
|
867
|
+
setSearchQuery(next);
|
|
868
|
+
setSearchCursor(searchCursor + input.length);
|
|
869
|
+
}
|
|
870
|
+
event.stopImmediatePropagation();
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
// After Enter closed the search bar, n/N keep walking the matches
|
|
874
|
+
// (CC: "Query persists across bar open/close so n/N keep working").
|
|
875
|
+
// Transcript mode only — in prompt mode n/N are ordinary input chars.
|
|
876
|
+
if (expanded && input === 'n' && searchQuery && searchCount > 0 && !key.ctrl && !key.meta) {
|
|
877
|
+
setSearchCurrent(i => (i >= searchCount - 1 ? 0 : i + 1));
|
|
878
|
+
event.stopImmediatePropagation();
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
if (expanded && input === 'N' && searchQuery && searchCount > 0 && !key.ctrl && !key.meta) {
|
|
882
|
+
setSearchCurrent(i => (i <= 0 ? searchCount - 1 : i - 1));
|
|
883
|
+
event.stopImmediatePropagation();
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
if (thinkingOpen) {
|
|
887
|
+
if (thinkingConfirm !== null) {
|
|
888
|
+
// Confirmation state: Enter applies, Esc backs out to the select.
|
|
889
|
+
if (key.return) {
|
|
890
|
+
const enabled = thinkingConfirm;
|
|
891
|
+
setThinkingVisible(enabled);
|
|
892
|
+
setThinkingConfirm(null);
|
|
893
|
+
setThinkingOpen(false);
|
|
894
|
+
channel.notify(`Thinking ${enabled ? 'on' : 'off'}`);
|
|
895
|
+
}
|
|
896
|
+
else if (key.escape) {
|
|
897
|
+
setThinkingConfirm(null);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
else if (key.upArrow || key.downArrow) {
|
|
901
|
+
setThinkingFocus(index => (index === 0 ? 1 : 0));
|
|
902
|
+
}
|
|
903
|
+
else if (key.return) {
|
|
904
|
+
const enabled = thinkingFocus === 0;
|
|
905
|
+
const midConversation = channel.rows.some(row => row.kind === 'assistant');
|
|
906
|
+
if (midConversation && enabled !== thinkingVisible) {
|
|
907
|
+
setThinkingConfirm(enabled);
|
|
908
|
+
}
|
|
909
|
+
else {
|
|
910
|
+
setThinkingVisible(enabled);
|
|
911
|
+
setThinkingOpen(false);
|
|
912
|
+
channel.notify(`Thinking ${enabled ? 'on' : 'off'}`);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
else if (key.escape) {
|
|
916
|
+
setThinkingOpen(false);
|
|
917
|
+
}
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
if (resumePickerOpen) {
|
|
921
|
+
if (key.upArrow) {
|
|
922
|
+
setResumeIndex(index => (index <= 0 ? resumeSessions.length - 1 : index - 1));
|
|
923
|
+
}
|
|
924
|
+
else if (key.downArrow) {
|
|
925
|
+
setResumeIndex(index => (index >= resumeSessions.length - 1 ? 0 : index + 1));
|
|
926
|
+
}
|
|
927
|
+
else if (key.return) {
|
|
928
|
+
const session = resumeSessions[resumeIndex];
|
|
929
|
+
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty list
|
|
930
|
+
if (session) {
|
|
931
|
+
// Enter switches the live agent to the persisted session right
|
|
932
|
+
// away (the history replays into the transcript); the resume.txt
|
|
933
|
+
// launcher marker is refreshed by resumeTo so `--resume` on the
|
|
934
|
+
// next launch opens the same session.
|
|
935
|
+
setResumePickerOpen(false);
|
|
936
|
+
void channel.resumeTo(session.id).then((ok) => {
|
|
937
|
+
if (ok)
|
|
938
|
+
channel.notify('Session resumed');
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
else {
|
|
942
|
+
setResumePickerOpen(false);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
else if (key.escape) {
|
|
946
|
+
setResumePickerOpen(false);
|
|
947
|
+
}
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
if (modelPickerOpen) {
|
|
951
|
+
if (key.upArrow) {
|
|
952
|
+
setModelIndex(index => (index <= 0 ? models.length - 1 : index - 1));
|
|
953
|
+
}
|
|
954
|
+
else if (key.downArrow) {
|
|
955
|
+
setModelIndex(index => (index >= models.length - 1 ? 0 : index + 1));
|
|
956
|
+
}
|
|
957
|
+
else if (key.return) {
|
|
958
|
+
const model = models[modelIndex];
|
|
959
|
+
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty list
|
|
960
|
+
if (model) {
|
|
961
|
+
// Enter switches the live model right away: the conversation is
|
|
962
|
+
// forked at its end and continued with an agent routed to the new
|
|
963
|
+
// model (history replays unchanged).
|
|
964
|
+
setModelPickerOpen(false);
|
|
965
|
+
channel.notify(`Switching model to ${model.name}…`);
|
|
966
|
+
void channel.switchModel(model.provider, model.id).then((ok) => {
|
|
967
|
+
if (ok)
|
|
968
|
+
channel.notify(`Model switched to ${model.name}`);
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
else {
|
|
972
|
+
setModelPickerOpen(false);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
else if (key.escape) {
|
|
976
|
+
setModelPickerOpen(false);
|
|
977
|
+
}
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
if (activityPickerOpen) {
|
|
981
|
+
if (key.upArrow) {
|
|
982
|
+
setActivityIndex(index => (index <= 0 ? PRESET_NAMES.length - 1 : index - 1));
|
|
983
|
+
}
|
|
984
|
+
else if (key.downArrow) {
|
|
985
|
+
setActivityIndex(index => (index >= PRESET_NAMES.length - 1 ? 0 : index + 1));
|
|
986
|
+
}
|
|
987
|
+
else if (key.return) {
|
|
988
|
+
const name = PRESET_NAMES[activityIndex];
|
|
989
|
+
setActivityPickerOpen(false);
|
|
990
|
+
if (name)
|
|
991
|
+
channel.setActivityFrames(name);
|
|
992
|
+
}
|
|
993
|
+
else if (key.escape) {
|
|
994
|
+
setActivityPickerOpen(false);
|
|
995
|
+
}
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
if (presetPickerOpen) {
|
|
999
|
+
if (key.upArrow) {
|
|
1000
|
+
setPresetIndex(index => (index <= 0 ? presetOptions.length - 1 : index - 1));
|
|
1001
|
+
}
|
|
1002
|
+
else if (key.downArrow) {
|
|
1003
|
+
setPresetIndex(index => (index >= presetOptions.length - 1 ? 0 : index + 1));
|
|
1004
|
+
}
|
|
1005
|
+
else if (key.return) {
|
|
1006
|
+
const option = presetOptions[presetIndex];
|
|
1007
|
+
setPresetPickerOpen(false);
|
|
1008
|
+
if (option)
|
|
1009
|
+
void channel.switchPreset(option.id);
|
|
1010
|
+
}
|
|
1011
|
+
else if (key.escape) {
|
|
1012
|
+
setPresetPickerOpen(false);
|
|
1013
|
+
}
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
if (themePickerOpen) {
|
|
1017
|
+
const options = getThemeOptions();
|
|
1018
|
+
if (key.upArrow) {
|
|
1019
|
+
setThemeIndex(index => (index <= 0 ? options.length - 1 : index - 1));
|
|
1020
|
+
}
|
|
1021
|
+
else if (key.downArrow) {
|
|
1022
|
+
setThemeIndex(index => (index >= options.length - 1 ? 0 : index + 1));
|
|
1023
|
+
}
|
|
1024
|
+
else if (key.return) {
|
|
1025
|
+
setThemePickerOpen(false);
|
|
1026
|
+
const name = options[themeIndex]?.value;
|
|
1027
|
+
if (name !== undefined) {
|
|
1028
|
+
const ok = setTheme(name);
|
|
1029
|
+
channel.notify(ok ? t('theme-switched-saved', { name }) : t('theme-switch-failed', { name }), { color: ok ? 'success' : 'error' });
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
else if (key.escape) {
|
|
1033
|
+
setThemePickerOpen(false);
|
|
1034
|
+
}
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
if (historyOpen) {
|
|
1038
|
+
if (key.escape) {
|
|
1039
|
+
setHistoryOpen(false);
|
|
1040
|
+
}
|
|
1041
|
+
else if (key.ctrl && (input === 'c' || input === 'd')) {
|
|
1042
|
+
// CC's history search cancels on ctrl+c/ctrl+d too.
|
|
1043
|
+
setHistoryOpen(false);
|
|
1044
|
+
}
|
|
1045
|
+
else if (key.return) {
|
|
1046
|
+
const entry = historyMatches[historyFocus];
|
|
1047
|
+
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty match list
|
|
1048
|
+
if (entry) {
|
|
1049
|
+
setHistoryFill(entry.text);
|
|
1050
|
+
setHistoryOpen(false);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
else if (key.upArrow) {
|
|
1054
|
+
setHistoryFocus(index => historyMatches.length === 0 ? 0 : (index <= 0 ? historyMatches.length - 1 : index - 1));
|
|
1055
|
+
}
|
|
1056
|
+
else if (key.downArrow || (key.ctrl && input === 'r')) {
|
|
1057
|
+
// CC's historySearch:next — ↓ and repeat ctrl+r walk to the next match.
|
|
1058
|
+
setHistoryFocus(index => historyMatches.length === 0 ? 0 : (index >= historyMatches.length - 1 ? 0 : index + 1));
|
|
1059
|
+
}
|
|
1060
|
+
else if (key.backspace) {
|
|
1061
|
+
if (historyCursor > 0) {
|
|
1062
|
+
const next = historyQuery.slice(0, historyCursor - 1) + historyQuery.slice(historyCursor);
|
|
1063
|
+
setHistoryQuery(next);
|
|
1064
|
+
setHistoryCursor(historyCursor - 1);
|
|
1065
|
+
setHistoryFocus(0);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
else if (key.delete) {
|
|
1069
|
+
if (historyCursor < historyQuery.length) {
|
|
1070
|
+
setHistoryQuery(historyQuery.slice(0, historyCursor) + historyQuery.slice(historyCursor + 1));
|
|
1071
|
+
setHistoryFocus(0);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
else if (key.leftArrow) {
|
|
1075
|
+
setHistoryCursor(cursor => Math.max(0, cursor - 1));
|
|
1076
|
+
}
|
|
1077
|
+
else if (key.rightArrow) {
|
|
1078
|
+
setHistoryCursor(cursor => Math.min(historyQuery.length, cursor + 1));
|
|
1079
|
+
}
|
|
1080
|
+
else if (key.home) {
|
|
1081
|
+
setHistoryCursor(0);
|
|
1082
|
+
}
|
|
1083
|
+
else if (key.end) {
|
|
1084
|
+
setHistoryCursor(historyQuery.length);
|
|
1085
|
+
}
|
|
1086
|
+
else if (!key.ctrl && !key.meta && input) {
|
|
1087
|
+
const next = historyQuery.slice(0, historyCursor) + input + historyQuery.slice(historyCursor);
|
|
1088
|
+
setHistoryQuery(next);
|
|
1089
|
+
setHistoryCursor(historyCursor + input.length);
|
|
1090
|
+
setHistoryFocus(0);
|
|
1091
|
+
}
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
if (rewindOpen) {
|
|
1095
|
+
if (rewindConfirm !== null) {
|
|
1096
|
+
// Confirmation state: Enter rewinds, Esc backs out to the list.
|
|
1097
|
+
if (key.return) {
|
|
1098
|
+
const row = rewindConfirm;
|
|
1099
|
+
setRewindOpen(false);
|
|
1100
|
+
setRewindConfirm(null);
|
|
1101
|
+
void performRewind(row);
|
|
1102
|
+
}
|
|
1103
|
+
else if (key.escape) {
|
|
1104
|
+
setRewindConfirm(null);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
else if (key.upArrow) {
|
|
1108
|
+
setRewindIndex(index => (index <= 0 ? rewindRows.length - 1 : index - 1));
|
|
1109
|
+
}
|
|
1110
|
+
else if (key.downArrow) {
|
|
1111
|
+
setRewindIndex(index => (index >= rewindRows.length - 1 ? 0 : index + 1));
|
|
1112
|
+
}
|
|
1113
|
+
else if (key.return) {
|
|
1114
|
+
const row = rewindRows[rewindIndex];
|
|
1115
|
+
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty list
|
|
1116
|
+
if (row)
|
|
1117
|
+
setRewindConfirm(row);
|
|
1118
|
+
}
|
|
1119
|
+
else if (key.escape) {
|
|
1120
|
+
setRewindOpen(false);
|
|
1121
|
+
setRewindConfirm(null);
|
|
1122
|
+
}
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
if (key.ctrl && input === 't') {
|
|
1126
|
+
// Toggle the startup loaded-context panel (keyboard only — the
|
|
1127
|
+
// ported ink core handles no mouse clicks).
|
|
1128
|
+
setLoadedContextOpen(previous => !previous);
|
|
1129
|
+
}
|
|
1130
|
+
if (key.ctrl && input === 'r' && !helpOpen) {
|
|
1131
|
+
setHistoryQuery('');
|
|
1132
|
+
setHistoryCursor(0);
|
|
1133
|
+
setHistoryFocus(0);
|
|
1134
|
+
setHistoryEntries(loadHistory());
|
|
1135
|
+
setHistoryOpen(true);
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
if (key.shift && key.upArrow && !selectionActive) {
|
|
1139
|
+
enterSelection();
|
|
1140
|
+
}
|
|
1141
|
+
else if (selectionActive) {
|
|
1142
|
+
if (key.upArrow) {
|
|
1143
|
+
moveSelection(-1);
|
|
1144
|
+
}
|
|
1145
|
+
else if (key.downArrow) {
|
|
1146
|
+
moveSelection(1);
|
|
1147
|
+
}
|
|
1148
|
+
else if (key.return && selectedId !== null) {
|
|
1149
|
+
toggleRowExpanded(selectedId);
|
|
1150
|
+
}
|
|
1151
|
+
else if (key.escape) {
|
|
1152
|
+
setSelectionActive(false);
|
|
1153
|
+
setSelectedId(null);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
else if (key.escape && channel.working) {
|
|
1157
|
+
// CC's chat:cancel — esc interrupts a running turn (the prompt input
|
|
1158
|
+
// only sees esc when idle, where it has the double-tap-clear meaning).
|
|
1159
|
+
// With messages queued for delivery, interrupt-and-deliver them right
|
|
1160
|
+
// away (Codex behavior); otherwise a plain interrupt parks the queue.
|
|
1161
|
+
if (channel.pending.length > 0) {
|
|
1162
|
+
const count = channel.interruptAndDeliver(channel.pending.map(item => item.text));
|
|
1163
|
+
if (count > 0) {
|
|
1164
|
+
channel.notify(t('interrupt-delivered', { n: count }), { timeoutMs: 2500 });
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
else {
|
|
1168
|
+
channel.cancel();
|
|
1169
|
+
}
|
|
1170
|
+
event.stopImmediatePropagation();
|
|
1171
|
+
}
|
|
1172
|
+
else if (key.ctrl && input === 'o') {
|
|
1173
|
+
// Leaving transcript mode (Ctrl+O) — search was already handled above.
|
|
1174
|
+
setExpanded(previous => !previous);
|
|
1175
|
+
}
|
|
1176
|
+
else if (input === '/' && !key.ctrl && !key.meta) {
|
|
1177
|
+
// `/` in transcript mode (Ctrl+O expanded, CC's REPL semantics:
|
|
1178
|
+
// search is active on the transcript screen where `/` isn't a command).
|
|
1179
|
+
if (expanded) {
|
|
1180
|
+
searchAnchorRef.current = handle?.getScrollTop() ?? 0;
|
|
1181
|
+
setSearchQuery('');
|
|
1182
|
+
setSearchCursor(0);
|
|
1183
|
+
setSearchCurrent(0);
|
|
1184
|
+
setSearchCount(0);
|
|
1185
|
+
setSearchOpen(true);
|
|
1186
|
+
event.stopImmediatePropagation();
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
else if (key.ctrl && (input === 'c' || input === 'd')) {
|
|
1190
|
+
// CC's app:exit — ctrl+c interrupts a running turn; idle ctrl+c
|
|
1191
|
+
// CLEARS a non-empty prompt (single press) and only arms the
|
|
1192
|
+
// double-press exit when the input is empty; ctrl+d keeps the
|
|
1193
|
+
// time-based double-press exit regardless.
|
|
1194
|
+
if (channel.working) {
|
|
1195
|
+
channel.cancel();
|
|
1196
|
+
}
|
|
1197
|
+
else if (input === 'c' && promptControllerRef.current?.hasText()) {
|
|
1198
|
+
promptControllerRef.current.clear();
|
|
1199
|
+
// A pending exit arm no longer makes sense once the user is editing.
|
|
1200
|
+
exitPendingRef.current = false;
|
|
1201
|
+
if (exitTimerRef.current)
|
|
1202
|
+
clearTimeout(exitTimerRef.current);
|
|
1203
|
+
}
|
|
1204
|
+
else {
|
|
1205
|
+
requestExit();
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
else if (key.ctrl && input === 'l') {
|
|
1209
|
+
// CC's app:redraw — clear the physical terminal and repaint.
|
|
1210
|
+
instances.get(process.stdout)?.forceRedraw();
|
|
1211
|
+
}
|
|
1212
|
+
else if (key.ctrl && input === 'e') {
|
|
1213
|
+
setShowAllMessages(previous => !previous);
|
|
1214
|
+
}
|
|
1215
|
+
else if (key.return && showPill) {
|
|
1216
|
+
handle?.scrollToBottom();
|
|
1217
|
+
}
|
|
1218
|
+
});
|
|
1219
|
+
// Working-activity line (spinner slot): context-pressure prefix shares the
|
|
1220
|
+
// StatusLine thresholds (amber ≥ 80, red ≥ 95).
|
|
1221
|
+
const activityWarnPct = contextPressurePct(channel.lastUsage, channel.contextWindow);
|
|
1222
|
+
/** Prompt input is inert while a modal dialog owns the keyboard. */
|
|
1223
|
+
const promptSelectionActive = selectionActive || modelPickerOpen || resumePickerOpen || activityPickerOpen ||
|
|
1224
|
+
presetPickerOpen || themePickerOpen || thinkingOpen || historyOpen || rewindOpen || searchOpen;
|
|
1225
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, width: "100%", children: [!isSticky && channel.lastUserText && (_jsx(StickyPromptHeader, { text: channel.lastUserText, onClick: () => {
|
|
1226
|
+
// Click jumps back to the pinned prompt (CC's StickyPromptHeader).
|
|
1227
|
+
const lastUser = [...channel.rows].reverse().find(row => row.kind === 'user');
|
|
1228
|
+
if (lastUser)
|
|
1229
|
+
seekRow(lastUser.id);
|
|
1230
|
+
else
|
|
1231
|
+
handle?.scrollToBottom();
|
|
1232
|
+
} })), _jsxs(ScrollBox, { ref: setHandle, flexDirection: "column", flexGrow: 1, flexShrink: 1, stickyScroll: true, children: [_jsx(LogoHeader, { model: channel.model, effort: channel.reasoningEffort, cwd: channel.cwd }), channel.rows.length === 0 && channel.loadedContext !== undefined && (_jsx(LoadedContextPanel, { context: channel.loadedContext, open: loadedContextOpen, onToggle: () => { setLoadedContextOpen(previous => !previous); } })), _jsx(MessageList, { rows: channel.rows, expanded: expanded, expandedRows: expandedRows, selectedId: selectionActive ? selectedId : null, onToggleRow: toggleRowExpanded, model: channel.model, showAll: showAllMessages, thinkingVisible: thinkingVisible, onToggleAll: () => { setShowAllMessages(previous => !previous); }, onLoadOlder: () => channel.loadOlder(), registerRowRef: registerRowRef, scrollHandle: handle, forceMountRowId: forceMountRowId, newSinceRowId: isSticky ? null : lastSeenRowIdRef.current, onUnseenCount: setUnseenCount })] }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [showPill && (_jsx(NewMessagesPill, { count: unseenCount, onClick: () => handle?.scrollToBottom() })), channel.working &&
|
|
1233
|
+
(channel.activityEnabled &&
|
|
1234
|
+
channel.workingActivity !== undefined &&
|
|
1235
|
+
channel.workingActivity.line !== '' &&
|
|
1236
|
+
channel.workingActivity.phase !== 'idle' ? (_jsx(Box, { marginTop: 1, children: _jsx(ActivityLine, { activity: channel.workingActivity, activityFrames: channel.activityFrames, warnPct: activityWarnPct, warnDanger: activityWarnPct !== undefined && activityWarnPct >= 95, suffix: ` · ↓ ${channel.responseChars} tokens` }) })) : (_jsx(WorkingSpinner, { mode: channel.spinnerMode, hasActiveTools: channel.activeToolCount > 0, responseLengthRef: responseLengthRef, loadingStartTimeRef: loadingStartTimeRef, totalPausedMsRef: totalPausedMsRef, pauseStartTimeRef: pauseStartTimeRef, thinkingStatus: thinkingStatus }))), thinkingOpen && (_jsx(ThinkingToggle, { currentValue: thinkingVisible, focusIndex: thinkingFocus, confirmationPending: thinkingConfirm })), resumePickerOpen && resumeSessions.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ResumePicker, { sessions: resumeSessions, focusIndex: resumeIndex, currentSessionId: channel.agentId }) })), modelPickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: models.length === 0 ? (_jsx(ModelPickerLoading, {})) : (_jsx(ModelPicker, { models: models, focusIndex: modelIndex, currentModel: `${channel.provider}/${channel.model}` })) })), activityPickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ActivityPicker, { focusIndex: activityIndex, currentPreset: channel.activityFrames }) })), presetPickerOpen && presetOptions.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(PresetPicker, { presets: presetOptions, focusIndex: presetIndex, currentPreset: channel.agentPreset }) })), themePickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ThemePicker, { focusIndex: themeIndex, currentTheme: themeName }) })), historyOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(HistorySearchDialog, { query: historyQuery, cursorOffset: historyCursor, matches: historyMatches, focusIndex: historyFocus }) })), rewindOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(RewindPicker, { rows: rewindRows, focusIndex: rewindIndex, confirmRow: rewindConfirm }) })), searchOpen && _jsx(TranscriptSearchBar, { query: searchQuery, cursorOffset: searchCursor, count: searchCount, current: searchCurrent }), _jsx(GoalTodoPanel, { channel: channel }), questionSnapshot !== null && (_jsx(AskUserQuestionPanel, { question: questionSnapshot.question, position: questionSnapshot.position, total: questionSnapshot.total, answered: questionSnapshot.answered, onAnswer: selection => questionStore.answerCurrent(selection), onCancel: () => questionStore.cancelCurrent() }, questionSnapshot.key)), questionSnapshot === null && (_jsx(PromptInput, { channel: channel, helpOpen: helpOpen, onToggleHelp: () => { setHelpOpen(previous => !previous); }, onRunCommand: runCommand, selectionActive: promptSelectionActive, fillText: historyFill, onFillConsumed: () => { setHistoryFill(null); }, onRewindRequest: openRewind, controllerRef: promptControllerRef })), _jsx(StatusLine, { channel: channel, selectionActive: selectionActive, helpOpen: helpOpen })] })] }));
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* The pinned prompt header shown above the ScrollBox while the user has
|
|
1240
|
+
* scrolled up (ported from the leak's FullscreenLayout.StickyPromptHeader).
|
|
1241
|
+
* Fixed at 1 row so the ScrollBox never shifts when the text changes.
|
|
1242
|
+
*/
|
|
1243
|
+
function StickyPromptHeader({ text, onClick, }) {
|
|
1244
|
+
const [hover, setHover] = React.useState(false);
|
|
1245
|
+
return (_jsx(Box, { flexShrink: 0, width: "100%", height: 1, paddingRight: 1, backgroundColor: hover ? 'userMessageBackgroundHover' : 'userMessageBackground', onMouseEnter: () => { setHover(true); }, onMouseLeave: () => { setHover(false); }, onClick: onClick, children: _jsxs(Text, { color: "subtle", wrap: "truncate-end", children: [POINTER, " ", text] }) }));
|
|
1246
|
+
}
|
|
1247
|
+
/** The `↓ N new messages` pill shown while scrolled up with new content. */
|
|
1248
|
+
function NewMessagesPill({ count, onClick, }) {
|
|
1249
|
+
const [hover, setHover] = React.useState(false);
|
|
1250
|
+
return (_jsx(Box, { paddingX: 2, paddingTop: 1, children: _jsx(Box, { backgroundColor: hover ? 'userMessageBackgroundHover' : 'background', onClick: onClick, onMouseEnter: () => { setHover(true); }, onMouseLeave: () => { setHover(false); }, children: _jsxs(Text, { color: "inverseText", bold: true, children: [' ', "\u2193 ", count === 1 ? '1 new message' : `${count} new messages`, ' '] }) }) }));
|
|
1251
|
+
}
|
|
1252
|
+
/** /model while the provider catalog is still loading (CC's LoadingState). */
|
|
1253
|
+
function ModelPickerLoading() {
|
|
1254
|
+
return (_jsx(Pane, { color: "permission", children: _jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Text, { bold: true, color: "permission", children: "Model" }), _jsx(LoadingState, { message: "Loading models", bold: true, subtitle: "Querying the provider\u2026" })] }) }));
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* The `/` incsearch bar (ported from CC's REPL TranscriptSearchBar): a
|
|
1258
|
+
* single row above the prompt input with the query, a block cursor, and the
|
|
1259
|
+
* match counter (`current/count`) or a red `no matches` when nothing hits.
|
|
1260
|
+
*/ function TranscriptSearchBar({ query, cursorOffset, count, current, }) {
|
|
1261
|
+
const cursorChar = cursorOffset < query.length ? query[cursorOffset] : ' ';
|
|
1262
|
+
return (_jsxs(NoSelect, { borderTopDimColor: true, borderBottom: false, borderLeft: false, borderRight: false, borderStyle: "single", marginTop: 1, paddingLeft: 2, width: "100%", children: [_jsx(Text, { children: "/" }), _jsx(Text, { children: query.slice(0, cursorOffset) }), _jsx(Text, { inverse: true, children: cursorChar }), cursorOffset < query.length && _jsx(Text, { children: query.slice(cursorOffset + 1) }), _jsx(Box, { flexGrow: 1 }), query && count === 0 ? (_jsx(Text, { color: "error", children: "no matches " })) : count > 0 ? (_jsxs(Text, { dimColor: true, children: [Math.min(current + 1, count), "/", count, ' '] })) : null] }));
|
|
1263
|
+
}
|