@deepseek-harness-tui/dsh-tui 0.6.1 → 0.7.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/README.md +74 -21
- package/bin/dsh-tui.js +115 -25
- package/cordis.patch.yml +69 -17
- package/cordis.yml +50 -12
- package/lib/types/activityPrefs.d.ts +1 -1
- package/lib/types/activityPrefs.js +3 -3
- package/lib/types/cc/figures.d.ts +27 -20
- package/lib/types/cc/figures.d.ts.map +1 -1
- package/lib/types/cc/figures.js +43 -34
- package/lib/types/cc/format.d.ts +18 -9
- package/lib/types/cc/format.d.ts.map +1 -1
- package/lib/types/cc/format.js +26 -18
- package/lib/types/cc/markdown.d.ts +31 -16
- package/lib/types/cc/markdown.d.ts.map +1 -1
- package/lib/types/cc/markdown.js +283 -260
- package/lib/types/cc/terminal.d.ts +10 -7
- package/lib/types/cc/terminal.d.ts.map +1 -1
- package/lib/types/cc/terminal.js +62 -50
- package/lib/types/command-trees.d.ts +3 -0
- package/lib/types/command-trees.d.ts.map +1 -0
- package/lib/types/command-trees.js +4 -0
- package/lib/types/commands.d.ts +37 -1
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +68 -3
- package/lib/types/components/ActivityLine.d.ts +1 -1
- package/lib/types/components/ActivityLine.d.ts.map +1 -1
- package/lib/types/components/ActivityPicker.d.ts.map +1 -1
- package/lib/types/components/ActivityPicker.js +3 -4
- package/lib/types/components/BtwPanel.d.ts +16 -0
- package/lib/types/components/BtwPanel.d.ts.map +1 -0
- package/lib/types/components/BtwPanel.js +47 -0
- package/lib/types/components/CommandSuggestions.d.ts +4 -2
- package/lib/types/components/CommandSuggestions.d.ts.map +1 -1
- package/lib/types/components/CommandSuggestions.js +2 -2
- package/lib/types/components/EffortSlider.d.ts +15 -0
- package/lib/types/components/EffortSlider.d.ts.map +1 -0
- package/lib/types/components/EffortSlider.js +17 -0
- package/lib/types/components/GoalTodoPanel.d.ts +1 -1
- package/lib/types/components/GoalTodoPanel.d.ts.map +1 -1
- package/lib/types/components/HelpMenu.d.ts +5 -2
- package/lib/types/components/HelpMenu.d.ts.map +1 -1
- package/lib/types/components/HelpMenu.js +8 -3
- package/lib/types/components/HistorySearchDialog.d.ts +1 -1
- package/lib/types/components/HistorySearchDialog.d.ts.map +1 -1
- package/lib/types/components/HistorySearchDialog.js +21 -10
- package/lib/types/components/InterruptedByUser.d.ts +2 -2
- package/lib/types/components/InterruptedByUser.d.ts.map +1 -1
- package/lib/types/components/InterruptedByUser.js +4 -3
- package/lib/types/components/LoadedContextPanel.d.ts +1 -1
- package/lib/types/components/LoadedContextPanel.d.ts.map +1 -1
- package/lib/types/components/LogoV2.d.ts.map +1 -1
- package/lib/types/components/LogoV2.js +2 -1
- package/lib/types/components/Markdown.d.ts +11 -11
- package/lib/types/components/Markdown.d.ts.map +1 -1
- package/lib/types/components/Markdown.js +63 -57
- package/lib/types/components/MarkdownTable.d.ts +1 -1
- package/lib/types/components/MarkdownTable.js +2 -2
- package/lib/types/components/MessageList.d.ts +10 -2
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +17 -10
- package/lib/types/components/ModelPicker.d.ts +4 -1
- package/lib/types/components/ModelPicker.d.ts.map +1 -1
- package/lib/types/components/ModelPicker.js +16 -4
- package/lib/types/components/OverlayAbove.d.ts +20 -0
- package/lib/types/components/OverlayAbove.d.ts.map +1 -0
- package/lib/types/components/OverlayAbove.js +18 -0
- package/lib/types/components/PresetPicker.d.ts +1 -1
- package/lib/types/components/PresetPicker.d.ts.map +1 -1
- package/lib/types/components/PresetPicker.js +2 -3
- package/lib/types/components/PromptInput.d.ts +5 -4
- package/lib/types/components/PromptInput.d.ts.map +1 -1
- package/lib/types/components/PromptInput.js +186 -39
- package/lib/types/components/ResumePicker.d.ts +15 -4
- package/lib/types/components/ResumePicker.d.ts.map +1 -1
- package/lib/types/components/ResumePicker.js +25 -9
- package/lib/types/components/RewindPicker.d.ts +1 -1
- package/lib/types/components/RewindPicker.d.ts.map +1 -1
- package/lib/types/components/RewindPicker.js +14 -5
- package/lib/types/components/SearchBox.d.ts +1 -1
- package/lib/types/components/SearchBox.js +1 -1
- package/lib/types/components/Spinner/GlimmerMessage.d.ts +1 -2
- package/lib/types/components/Spinner/GlimmerMessage.d.ts.map +1 -1
- package/lib/types/components/Spinner/GlimmerMessage.js +1 -2
- package/lib/types/components/Spinner/SpinnerAnimationRow.d.ts +1 -1
- package/lib/types/components/Spinner/SpinnerAnimationRow.js +2 -2
- package/lib/types/components/Spinner/SpinnerGlyph.d.ts +1 -1
- package/lib/types/components/Spinner/SpinnerGlyph.js +1 -1
- package/lib/types/components/Spinner/spinnerUtils.d.ts +13 -18
- package/lib/types/components/Spinner/spinnerUtils.d.ts.map +1 -1
- package/lib/types/components/Spinner/spinnerUtils.js +67 -71
- package/lib/types/components/Spinner/useStalledAnimation.d.ts +1 -2
- package/lib/types/components/Spinner/useStalledAnimation.d.ts.map +1 -1
- package/lib/types/components/Spinner/useStalledAnimation.js +1 -2
- package/lib/types/components/StreamingMarkdown.d.ts +1 -1
- package/lib/types/components/StreamingMarkdown.js +1 -1
- package/lib/types/components/ThemePicker.d.ts +13 -9
- package/lib/types/components/ThemePicker.d.ts.map +1 -1
- package/lib/types/components/ThemePicker.js +21 -15
- package/lib/types/components/ThinkingToggle.d.ts +1 -1
- package/lib/types/components/ThinkingToggle.d.ts.map +1 -1
- package/lib/types/components/ThinkingToggle.js +8 -8
- package/lib/types/components/ToolUseLoader.d.ts +1 -1
- package/lib/types/components/ToolUseLoader.js +1 -1
- package/lib/types/components/WorkingSpinner.d.ts +2 -2
- package/lib/types/components/WorkingSpinner.js +2 -2
- package/lib/types/components/WorkspaceFlowPicker.d.ts +15 -0
- package/lib/types/components/WorkspaceFlowPicker.d.ts.map +1 -0
- package/lib/types/components/WorkspaceFlowPicker.js +19 -0
- package/lib/types/components/WorkspacePicker.d.ts +9 -0
- package/lib/types/components/WorkspacePicker.d.ts.map +1 -0
- package/lib/types/components/WorkspacePicker.js +13 -0
- package/lib/types/components/approvals/ApprovalPanel.d.ts +1 -1
- package/lib/types/components/approvals/ApprovalPanel.d.ts.map +1 -1
- package/lib/types/components/approvals/ApprovalPanel.js +2 -1
- package/lib/types/components/design-system/Byline.d.ts +1 -1
- package/lib/types/components/design-system/Byline.js +1 -1
- package/lib/types/components/design-system/Divider.d.ts +1 -1
- package/lib/types/components/design-system/Divider.js +1 -1
- package/lib/types/components/design-system/HintLine.d.ts +12 -0
- package/lib/types/components/design-system/HintLine.d.ts.map +1 -0
- package/lib/types/components/design-system/HintLine.js +15 -0
- package/lib/types/components/design-system/KeyboardShortcutHint.d.ts +1 -1
- package/lib/types/components/design-system/KeyboardShortcutHint.js +1 -1
- package/lib/types/components/design-system/ListItem.d.ts +2 -2
- package/lib/types/components/design-system/ListItem.d.ts.map +1 -1
- package/lib/types/components/design-system/ListItem.js +35 -3
- package/lib/types/components/design-system/LoadingState.d.ts +1 -2
- package/lib/types/components/design-system/LoadingState.d.ts.map +1 -1
- package/lib/types/components/design-system/LoadingState.js +1 -2
- package/lib/types/components/design-system/Pane.d.ts +2 -2
- package/lib/types/components/design-system/Pane.js +2 -2
- package/lib/types/components/design-system/ProgressBar.d.ts +1 -2
- package/lib/types/components/design-system/ProgressBar.d.ts.map +1 -1
- package/lib/types/components/design-system/ProgressBar.js +1 -2
- package/lib/types/components/design-system/StatusIcon.d.ts +2 -2
- package/lib/types/components/design-system/StatusIcon.js +2 -2
- package/lib/types/components/design-system/ThemeProvider.d.ts +1 -1
- package/lib/types/components/design-system/ThemeProvider.d.ts.map +1 -1
- package/lib/types/components/design-system/ThemeProvider.js +87 -26
- package/lib/types/components/design-system/ThemedBox.d.ts +1 -1
- package/lib/types/components/design-system/ThemedBox.js +1 -1
- package/lib/types/components/design-system/ThemedText.d.ts +2 -2
- package/lib/types/components/design-system/ThemedText.js +2 -2
- package/lib/types/components/listWindow.d.ts +26 -0
- package/lib/types/components/listWindow.d.ts.map +1 -0
- package/lib/types/components/listWindow.js +47 -0
- package/lib/types/components/messages/AssistantTextMessage.d.ts +1 -2
- package/lib/types/components/messages/AssistantTextMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantTextMessage.js +1 -2
- package/lib/types/components/messages/AssistantThinkingMessage.d.ts +2 -2
- package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantThinkingMessage.js +6 -6
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts +12 -4
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantToolUseMessage.js +9 -5
- package/lib/types/components/messages/MessageMetadata.d.ts +1 -1
- package/lib/types/components/messages/MessageMetadata.js +1 -1
- package/lib/types/components/messages/UserPromptMessage.d.ts +1 -1
- package/lib/types/components/messages/UserPromptMessage.js +1 -1
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts +5 -1
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
- package/lib/types/components/questions/AskUserQuestionPanel.js +11 -9
- package/lib/types/components/questions/PlanReviewPanel.d.ts +1 -1
- package/lib/types/components/questions/PlanReviewPanel.d.ts.map +1 -1
- package/lib/types/components/questions/PlanReviewPanel.js +3 -2
- package/lib/types/components/trajectory/HotspotView.d.ts +14 -0
- package/lib/types/components/trajectory/HotspotView.d.ts.map +1 -0
- package/lib/types/components/trajectory/HotspotView.js +83 -0
- package/lib/types/components/trajectory/Inspector.d.ts +31 -0
- package/lib/types/components/trajectory/Inspector.d.ts.map +1 -0
- package/lib/types/components/trajectory/Inspector.js +67 -0
- package/lib/types/components/trajectory/Ledger.d.ts +20 -0
- package/lib/types/components/trajectory/Ledger.d.ts.map +1 -0
- package/lib/types/components/trajectory/Ledger.js +112 -0
- package/lib/types/components/trajectory/MiniWake.d.ts +24 -0
- package/lib/types/components/trajectory/MiniWake.d.ts.map +1 -0
- package/lib/types/components/trajectory/MiniWake.js +99 -0
- package/lib/types/components/trajectory/WaveBand.d.ts +33 -0
- package/lib/types/components/trajectory/WaveBand.d.ts.map +1 -0
- package/lib/types/components/trajectory/WaveBand.js +145 -0
- package/lib/types/customTheme.d.ts +6 -6
- package/lib/types/customTheme.d.ts.map +1 -1
- package/lib/types/customTheme.js +15 -12
- package/lib/types/dsh-adapter/approvals.d.ts.map +1 -0
- package/lib/types/{channel.d.ts → dsh-adapter/channel.d.ts} +184 -43
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -0
- package/lib/types/{channel.js → dsh-adapter/channel.js} +972 -201
- package/lib/types/dsh-adapter/childStderr.d.ts.map +1 -0
- package/lib/types/{childStderr.js → dsh-adapter/childStderr.js} +1 -1
- package/lib/types/dsh-adapter/command-trees.d.ts +27 -0
- package/lib/types/dsh-adapter/command-trees.d.ts.map +1 -0
- package/lib/types/dsh-adapter/command-trees.js +42 -0
- package/lib/types/dsh-adapter/compat/index.d.ts +22 -0
- package/lib/types/dsh-adapter/compat/index.d.ts.map +1 -0
- package/lib/types/{compat → dsh-adapter/compat}/index.js +7 -4
- package/lib/types/dsh-adapter/compat/sessionLog.d.ts +86 -0
- package/lib/types/dsh-adapter/compat/sessionLog.d.ts.map +1 -0
- package/lib/types/dsh-adapter/compat/sessionLog.js +347 -0
- package/lib/types/dsh-adapter/contract.d.ts +20 -0
- package/lib/types/dsh-adapter/contract.d.ts.map +1 -0
- package/lib/types/dsh-adapter/contract.js +109 -0
- package/lib/types/dsh-adapter/index.d.ts +80 -0
- package/lib/types/dsh-adapter/index.d.ts.map +1 -0
- package/lib/types/dsh-adapter/index.js +45 -0
- package/lib/types/dsh-adapter/invariant.d.ts.map +1 -0
- package/lib/types/dsh-adapter/packaged-skills.d.ts.map +1 -0
- package/lib/types/{plugin.d.ts → dsh-adapter/plugin.d.ts} +17 -0
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -0
- package/lib/types/{plugin.js → dsh-adapter/plugin.js} +280 -80
- package/lib/types/dsh-adapter/presets.d.ts.map +1 -0
- package/lib/types/dsh-adapter/providerWizard.d.ts +91 -0
- package/lib/types/dsh-adapter/providerWizard.d.ts.map +1 -0
- package/lib/types/dsh-adapter/providerWizard.js +332 -0
- package/lib/types/{questions.d.ts → dsh-adapter/questions.d.ts} +7 -2
- package/lib/types/dsh-adapter/questions.d.ts.map +1 -0
- package/lib/types/{questions.js → dsh-adapter/questions.js} +15 -7
- package/lib/types/dsh-adapter/sideQuestion.d.ts +38 -0
- package/lib/types/dsh-adapter/sideQuestion.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sideQuestion.js +69 -0
- package/lib/types/dsh-adapter/trajectory/aggregate.d.ts +46 -0
- package/lib/types/dsh-adapter/trajectory/aggregate.d.ts.map +1 -0
- package/lib/types/dsh-adapter/trajectory/aggregate.js +207 -0
- package/lib/types/dsh-adapter/trajectory/guards.d.ts +177 -0
- package/lib/types/dsh-adapter/trajectory/guards.d.ts.map +1 -0
- package/lib/types/dsh-adapter/trajectory/guards.js +235 -0
- package/lib/types/dsh-adapter/trajectory/index.d.ts +16 -0
- package/lib/types/dsh-adapter/trajectory/index.d.ts.map +1 -0
- package/lib/types/dsh-adapter/trajectory/index.js +15 -0
- package/lib/types/dsh-adapter/trajectory/inspect.d.ts +40 -0
- package/lib/types/dsh-adapter/trajectory/inspect.d.ts.map +1 -0
- package/lib/types/dsh-adapter/trajectory/inspect.js +160 -0
- package/lib/types/dsh-adapter/trajectory/projection.d.ts +128 -0
- package/lib/types/dsh-adapter/trajectory/projection.d.ts.map +1 -0
- package/lib/types/dsh-adapter/trajectory/projection.js +653 -0
- package/lib/types/dsh-adapter/trajectory/types.d.ts +232 -0
- package/lib/types/dsh-adapter/trajectory/types.d.ts.map +1 -0
- package/lib/types/dsh-adapter/trajectory/types.js +70 -0
- package/lib/types/dsh-adapter/trajectory/wave.d.ts +45 -0
- package/lib/types/dsh-adapter/trajectory/wave.d.ts.map +1 -0
- package/lib/types/dsh-adapter/trajectory/wave.js +210 -0
- package/lib/types/dsh-adapter/types.d.ts +23 -0
- package/lib/types/dsh-adapter/types.d.ts.map +1 -0
- package/lib/types/dsh-adapter/types.js +1 -0
- package/lib/types/dsh-adapter/workspace.d.ts +22 -0
- package/lib/types/dsh-adapter/workspace.d.ts.map +1 -0
- package/lib/types/dsh-adapter/workspace.js +16 -0
- package/lib/types/dsh-adapter/workspaces.d.ts +118 -0
- package/lib/types/dsh-adapter/workspaces.d.ts.map +1 -0
- package/lib/types/dsh-adapter/workspaces.js +290 -0
- package/lib/types/effortPrefs.d.ts +1 -1
- package/lib/types/effortPrefs.js +3 -3
- package/lib/types/history.js +2 -2
- package/lib/types/hooks/useBlink.d.ts +1 -1
- package/lib/types/hooks/useBlink.js +1 -1
- package/lib/types/i18n.d.ts +977 -36
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +307 -30
- package/lib/types/index.d.ts +1 -69
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +3 -31
- package/lib/types/ink/components/App.d.ts.map +1 -1
- package/lib/types/ink/components/App.js +33 -19
- package/lib/types/ink/dom.d.ts +15 -0
- package/lib/types/ink/dom.d.ts.map +1 -1
- package/lib/types/ink/dom.js +32 -0
- package/lib/types/ink/hooks/use-input.d.ts.map +1 -1
- package/lib/types/ink/hooks/use-input.js +8 -0
- package/lib/types/ink/hooks/use-terminal-size.d.ts +1 -1
- package/lib/types/ink/hooks/use-terminal-size.js +1 -1
- package/lib/types/ink/ink.d.ts.map +1 -1
- package/lib/types/ink/ink.js +109 -28
- package/lib/types/ink/input-suppression.d.ts +20 -0
- package/lib/types/ink/input-suppression.d.ts.map +1 -0
- package/lib/types/ink/input-suppression.js +26 -0
- package/lib/types/ink/log-update.js +21 -6
- package/lib/types/ink/parse-keypress.d.ts +31 -0
- package/lib/types/ink/parse-keypress.d.ts.map +1 -1
- package/lib/types/ink/parse-keypress.js +442 -8
- package/lib/types/ink/terminal-querier.d.ts +5 -1
- package/lib/types/ink/terminal-querier.d.ts.map +1 -1
- package/lib/types/ink/terminal-querier.js +27 -3
- package/lib/types/ink/terminal.d.ts +18 -0
- package/lib/types/ink/terminal.d.ts.map +1 -1
- package/lib/types/ink/terminal.js +32 -4
- package/lib/types/ink/termio/csi.d.ts +10 -0
- package/lib/types/ink/termio/csi.d.ts.map +1 -1
- package/lib/types/ink/termio/csi.js +20 -0
- package/lib/types/ink/termio/tokenize.js +10 -0
- package/lib/types/modelPrefs.d.ts +1 -1
- package/lib/types/modelPrefs.js +3 -3
- package/lib/types/presetPrefs.d.ts +1 -1
- package/lib/types/presetPrefs.js +3 -3
- package/lib/types/screens/Chat.d.ts +20 -4
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +655 -55
- package/lib/types/screens/StatusLine.d.ts +17 -2
- package/lib/types/screens/StatusLine.d.ts.map +1 -1
- package/lib/types/screens/StatusLine.js +12 -4
- package/lib/types/screens/TrajectoryScene.d.ts +16 -0
- package/lib/types/screens/TrajectoryScene.d.ts.map +1 -0
- package/lib/types/screens/TrajectoryScene.js +305 -0
- package/lib/types/sessionHistory.d.ts +9 -1
- package/lib/types/sessionHistory.d.ts.map +1 -1
- package/lib/types/sessionHistory.js +56 -16
- package/lib/types/sessionModes.d.ts +29 -0
- package/lib/types/sessionModes.d.ts.map +1 -0
- package/lib/types/sessionModes.js +45 -0
- package/lib/types/theme.d.ts +27 -3
- package/lib/types/theme.d.ts.map +1 -1
- package/lib/types/theme.js +48 -4
- package/lib/types/themePrefs.d.ts +3 -3
- package/lib/types/themePrefs.js +5 -5
- package/lib/types/trajectory/format.d.ts +100 -0
- package/lib/types/trajectory/format.d.ts.map +1 -0
- package/lib/types/trajectory/format.js +213 -0
- package/lib/types/trajectory/motion.d.ts +99 -0
- package/lib/types/trajectory/motion.d.ts.map +1 -0
- package/lib/types/trajectory/motion.js +143 -0
- package/lib/types/trajectory/query.d.ts +83 -0
- package/lib/types/trajectory/query.d.ts.map +1 -0
- package/lib/types/trajectory/query.js +187 -0
- package/lib/types/trajectoryPrefs.d.ts +36 -0
- package/lib/types/trajectoryPrefs.d.ts.map +1 -0
- package/lib/types/trajectoryPrefs.js +65 -0
- package/lib/types/update.d.ts +3 -3
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +18 -7
- package/lib/types/utils/clipboard.d.ts +101 -29
- package/lib/types/utils/clipboard.d.ts.map +1 -1
- package/lib/types/utils/clipboard.js +533 -55
- package/lib/types/utils/debug.d.ts +1 -1
- package/lib/types/utils/debug.js +2 -2
- package/lib/types/utils/execFileNoThrow.d.ts.map +1 -1
- package/lib/types/utils/execFileNoThrow.js +16 -6
- package/lib/types/utils/externalEditor.d.ts +115 -0
- package/lib/types/utils/externalEditor.d.ts.map +1 -0
- package/lib/types/utils/externalEditor.js +270 -0
- package/lib/types/utils/fullscreen.d.ts +2 -2
- package/lib/types/utils/fullscreen.js +3 -3
- package/lib/types/utils/loaded-context.d.ts +1 -1
- package/lib/types/utils/loaded-context.d.ts.map +1 -1
- package/lib/types/utils/modifiers.d.ts +24 -0
- package/lib/types/utils/modifiers.d.ts.map +1 -1
- package/lib/types/utils/modifiers.js +27 -0
- package/lib/types/utils/paths.d.ts +45 -0
- package/lib/types/utils/paths.d.ts.map +1 -0
- package/lib/types/utils/paths.js +69 -0
- package/lib/types/utils/shellQuote.d.ts +14 -0
- package/lib/types/utils/shellQuote.d.ts.map +1 -0
- package/lib/types/utils/shellQuote.js +39 -0
- package/lib/types/utils/workspaceRoot.d.ts +21 -0
- package/lib/types/utils/workspaceRoot.d.ts.map +1 -0
- package/lib/types/utils/workspaceRoot.js +47 -0
- package/lib/types/workspaces.d.ts +3 -0
- package/lib/types/workspaces.d.ts.map +1 -0
- package/lib/types/workspaces.js +4 -0
- package/package.json +25 -7
- package/lib/types/approvals.d.ts.map +0 -1
- package/lib/types/channel.d.ts.map +0 -1
- package/lib/types/childStderr.d.ts.map +0 -1
- package/lib/types/compat/index.d.ts +0 -20
- package/lib/types/compat/index.d.ts.map +0 -1
- package/lib/types/compat/sessionLog.d.ts +0 -40
- package/lib/types/compat/sessionLog.d.ts.map +0 -1
- package/lib/types/compat/sessionLog.js +0 -226
- package/lib/types/invariant.d.ts.map +0 -1
- package/lib/types/packaged-skills.d.ts.map +0 -1
- package/lib/types/plugin.d.ts.map +0 -1
- package/lib/types/presets.d.ts.map +0 -1
- package/lib/types/questions.d.ts.map +0 -1
- /package/lib/types/{approvals.d.ts → dsh-adapter/approvals.d.ts} +0 -0
- /package/lib/types/{approvals.js → dsh-adapter/approvals.js} +0 -0
- /package/lib/types/{childStderr.d.ts → dsh-adapter/childStderr.d.ts} +0 -0
- /package/lib/types/{invariant.d.ts → dsh-adapter/invariant.d.ts} +0 -0
- /package/lib/types/{invariant.js → dsh-adapter/invariant.js} +0 -0
- /package/lib/types/{packaged-skills.d.ts → dsh-adapter/packaged-skills.d.ts} +0 -0
- /package/lib/types/{packaged-skills.js → dsh-adapter/packaged-skills.js} +0 -0
- /package/lib/types/{presets.d.ts → dsh-adapter/presets.d.ts} +0 -0
- /package/lib/types/{presets.js → dsh-adapter/presets.js} +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { t, getLang, setLang, isLang, writeLangPref, subscribeLang } from '../i18n.js';
|
|
4
|
-
import { Box, Text, useInput, ScrollBox, useTheme } from '../ui.js';
|
|
4
|
+
import { AlternateScreen, Box, Text, useInput, ScrollBox, useTheme, useTerminalSize } from '../ui.js';
|
|
5
5
|
import { POINTER } from '../cc/figures.js';
|
|
6
|
-
import { isMod, modLabel } from '../utils/modifiers.js';
|
|
6
|
+
import { isMod, isPlainReturnInput, modLabel } from '../utils/modifiers.js';
|
|
7
7
|
import { formatTokens } from '../cc/format.js';
|
|
8
|
-
import {
|
|
8
|
+
import { runProviderWizard } from '../dsh-adapter/providerWizard.js';
|
|
9
|
+
import { ApprovalStore } from '../dsh-adapter/approvals.js';
|
|
9
10
|
import { AskUserQuestionPanel } from '../components/questions/AskUserQuestionPanel.js';
|
|
10
11
|
import { ApprovalPanel } from '../components/approvals/ApprovalPanel.js';
|
|
11
12
|
import { useSearchHighlight } from '../ink/hooks/use-search-highlight.js';
|
|
@@ -14,8 +15,8 @@ import { useTerminalFocus } from '../ink/hooks/use-terminal-focus.js';
|
|
|
14
15
|
import { useCopyOnSelect } from '../ink/hooks/use-copy-on-select.js';
|
|
15
16
|
import { useSelection } from '../ink/hooks/use-selection.js';
|
|
16
17
|
import { NoSelect } from '../ink/components/NoSelect.js';
|
|
17
|
-
import instances from '../ink/instances.js';
|
|
18
18
|
import { LogoHeader, MessageList } from '../components/MessageList.js';
|
|
19
|
+
import { OverlayAbove } from '../components/OverlayAbove.js';
|
|
19
20
|
import { PromptInput } from '../components/PromptInput.js';
|
|
20
21
|
import { GoalTodoPanel } from '../components/GoalTodoPanel.js';
|
|
21
22
|
import { LoadedContextPanel } from '../components/LoadedContextPanel.js';
|
|
@@ -24,16 +25,30 @@ import { WorkingSpinner, useThinkingStatus } from '../components/WorkingSpinner.
|
|
|
24
25
|
import { ActivityLine, contextPressurePct } from '../components/ActivityLine.js';
|
|
25
26
|
import { ModelPicker } from '../components/ModelPicker.js';
|
|
26
27
|
import { ResumePicker } from '../components/ResumePicker.js';
|
|
28
|
+
import { WorkspacePicker } from '../components/WorkspacePicker.js';
|
|
29
|
+
import { WorkspaceFlowPicker } from '../components/WorkspaceFlowPicker.js';
|
|
27
30
|
import { ActivityPicker } from '../components/ActivityPicker.js';
|
|
31
|
+
import { EffortSlider } from '../components/EffortSlider.js';
|
|
28
32
|
import { PresetPicker } from '../components/PresetPicker.js';
|
|
29
33
|
import { ThemePicker, getThemeOptions } from '../components/ThemePicker.js';
|
|
34
|
+
import { AUTO_THEME_NAME, getAutoThemeBase } from '../theme.js';
|
|
30
35
|
import { FRAME_PRESETS, PRESET_NAMES } from '../components/activityFrames.js';
|
|
31
36
|
import { ThinkingToggle } from '../components/ThinkingToggle.js';
|
|
32
37
|
import { HistorySearchDialog } from '../components/HistorySearchDialog.js';
|
|
33
38
|
import { RewindPicker } from '../components/RewindPicker.js';
|
|
39
|
+
import { BtwPanel } from '../components/BtwPanel.js';
|
|
40
|
+
import { setClipboard } from '../ink/termio/osc.js';
|
|
41
|
+
import instances from '../ink/instances.js';
|
|
42
|
+
import { useAnimationFrame } from '../ink/hooks/use-animation-frame.js';
|
|
43
|
+
import { TrajectoryScene } from './TrajectoryScene.js';
|
|
44
|
+
import { extendTrajectory, projectWave } from '../dsh-adapter/trajectory/index.js';
|
|
45
|
+
import { miniWakeWidth } from '../components/trajectory/MiniWake.js';
|
|
46
|
+
import { readTrajectorySeen, writeTrajectorySeen } from '../trajectoryPrefs.js';
|
|
34
47
|
import { LoadingState } from '../components/design-system/LoadingState.js';
|
|
35
48
|
import { Pane } from '../components/design-system/Pane.js';
|
|
36
49
|
import { loadHistory } from '../history.js';
|
|
50
|
+
/** Shared empty snapshot for hosts whose channel has no event log. */
|
|
51
|
+
const NO_EVENTS = [];
|
|
37
52
|
/** Row kinds the message-selection cursor can land on. */
|
|
38
53
|
const SELECTABLE_KINDS = new Set([
|
|
39
54
|
'user',
|
|
@@ -100,7 +115,7 @@ function searchableText(row) {
|
|
|
100
115
|
* snapshot stays null and the approval panel never mounts.
|
|
101
116
|
*/
|
|
102
117
|
let fallbackApprovalStore;
|
|
103
|
-
export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate, }) {
|
|
118
|
+
export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate, fullscreen = false, trajectorySeen: trajectorySeenProp, }) {
|
|
104
119
|
// Re-render whenever the channel mutates; rows/status are read fresh below.
|
|
105
120
|
React.useSyncExternalStore(channel.subscribe, () => channel.version);
|
|
106
121
|
// Re-render on language switches so the whole UI hot-swaps its strings.
|
|
@@ -138,6 +153,19 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
138
153
|
const [resumePickerOpen, setResumePickerOpen] = React.useState(false);
|
|
139
154
|
const [resumeSessions, setResumeSessions] = React.useState([]);
|
|
140
155
|
const [resumeIndex, setResumeIndex] = React.useState(0);
|
|
156
|
+
/** `/resume` session management (issue #112): plain selection, a delete
|
|
157
|
+
* confirmation (ctrl+d), or the inline rename input (ctrl+r). */
|
|
158
|
+
const [resumeMode, setResumeMode] = React.useState('list');
|
|
159
|
+
const [workspacePickerOpen, setWorkspacePickerOpen] = React.useState(false);
|
|
160
|
+
const [workspaceTargets, setWorkspaceTargets] = React.useState([]);
|
|
161
|
+
const [workspaceIndex, setWorkspaceIndex] = React.useState(0);
|
|
162
|
+
const [workspaceFlow, setWorkspaceFlow] = React.useState(null);
|
|
163
|
+
const [workspaceFlowIndex, setWorkspaceFlowIndex] = React.useState(0);
|
|
164
|
+
const [workspaceFlowBusy, setWorkspaceFlowBusy] = React.useState(false);
|
|
165
|
+
const [workspaceFlowInput, setWorkspaceFlowInput] = React.useState(null);
|
|
166
|
+
const workspaceFlowRequestRef = React.useRef(0);
|
|
167
|
+
const workspaceFlowAbortRef = React.useRef(null);
|
|
168
|
+
const [resumeRenameText, setResumeRenameText] = React.useState('');
|
|
141
169
|
/** `/activity` indicator picker (pi extension's interactive select). */
|
|
142
170
|
const [activityPickerOpen, setActivityPickerOpen] = React.useState(false);
|
|
143
171
|
const [activityIndex, setActivityIndex] = React.useState(0);
|
|
@@ -145,10 +173,15 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
145
173
|
const [presetPickerOpen, setPresetPickerOpen] = React.useState(false);
|
|
146
174
|
const [presetOptions, setPresetOptions] = React.useState([]);
|
|
147
175
|
const [presetIndex, setPresetIndex] = React.useState(0);
|
|
148
|
-
/** `/
|
|
176
|
+
/** `/effort` rheostat slider: adapter levels load async, focus moves ←/→. */
|
|
177
|
+
const [effortSliderOpen, setEffortSliderOpen] = React.useState(false);
|
|
178
|
+
const [effortOptions, setEffortOptions] = React.useState([]);
|
|
179
|
+
const [effortIndex, setEffortIndex] = React.useState(0);
|
|
180
|
+
/** `/theme` color-theme picker (built-ins + ~/.dsh-tui/themes user themes). */
|
|
149
181
|
const [themePickerOpen, setThemePickerOpen] = React.useState(false);
|
|
150
182
|
const [themeIndex, setThemeIndex] = React.useState(0);
|
|
151
183
|
const [themeName, setTheme] = useTheme();
|
|
184
|
+
const { rows: terminalRows } = useTerminalSize();
|
|
152
185
|
const [showAllMessages, setShowAllMessages] = React.useState(false);
|
|
153
186
|
const [thinkingVisible, setThinkingVisible] = React.useState(true);
|
|
154
187
|
const [thinkingOpen, setThinkingOpen] = React.useState(false);
|
|
@@ -166,6 +199,50 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
166
199
|
const [rewindOpen, setRewindOpen] = React.useState(false);
|
|
167
200
|
const [rewindIndex, setRewindIndex] = React.useState(0);
|
|
168
201
|
const [rewindConfirm, setRewindConfirm] = React.useState(null);
|
|
202
|
+
/** /btw side-question overlay (CC): pure UI state — the answer never
|
|
203
|
+
* enters the transcript or the session log. */
|
|
204
|
+
const [btw, setBtw] = React.useState(null);
|
|
205
|
+
const btwAbortRef = React.useRef(null);
|
|
206
|
+
const closeBtw = () => {
|
|
207
|
+
btwAbortRef.current?.abort();
|
|
208
|
+
btwAbortRef.current = null;
|
|
209
|
+
setBtw(null);
|
|
210
|
+
};
|
|
211
|
+
React.useEffect(() => () => btwAbortRef.current?.abort(), []);
|
|
212
|
+
/**
|
|
213
|
+
* The trajectory scene (issue #80 evolution). Unlike every other overlay
|
|
214
|
+
* here it is not a panel but a whole screen: while open, Chat renders the
|
|
215
|
+
* scene INSTEAD of the conversation (see the early return below) and hands
|
|
216
|
+
* it the keyboard. Chat itself stays mounted, so scroll position, pickers
|
|
217
|
+
* and in-flight turn state survive the round trip untouched.
|
|
218
|
+
*/
|
|
219
|
+
const [sceneOpen, setSceneOpen] = React.useState(false);
|
|
220
|
+
/**
|
|
221
|
+
* Close the scene.
|
|
222
|
+
*
|
|
223
|
+
* Leaving the alternate screen makes the terminal restore the main buffer
|
|
224
|
+
* itself; Ink then repaints once, because `setAltScreenActive(false)` blanks
|
|
225
|
+
* its front frame. In inline mode that costs one frame of scrollback per
|
|
226
|
+
* round trip — the same, already-accepted cost as the Ctrl+X external-editor
|
|
227
|
+
* handoff, and bounded per OPEN rather than per keystroke. Making it zero
|
|
228
|
+
* needs the render core to save and restore the pre-alt front frame, which
|
|
229
|
+
* is a separate change to `setAltScreenActive` and deliberately not made
|
|
230
|
+
* here. `verify-trace-scene` pins the property that matters meanwhile:
|
|
231
|
+
* navigating inside the scene adds nothing at all.
|
|
232
|
+
*/
|
|
233
|
+
const closeScene = React.useCallback(() => {
|
|
234
|
+
setSceneOpen(false);
|
|
235
|
+
}, []);
|
|
236
|
+
/** Open the scene, mark failures seen, and retire the key hint for good. */
|
|
237
|
+
const openScene = React.useCallback(() => {
|
|
238
|
+
seenFailuresRef.current = trajectoryRef.current?.counts.errors ?? 0;
|
|
239
|
+
setTrajectorySeen(previous => {
|
|
240
|
+
if (!previous)
|
|
241
|
+
writeTrajectorySeen();
|
|
242
|
+
return true;
|
|
243
|
+
});
|
|
244
|
+
setSceneOpen(true);
|
|
245
|
+
}, []);
|
|
169
246
|
/** Startup context panel: expanded by header click or Ctrl+T. */
|
|
170
247
|
const [loadedContextOpen, setLoadedContextOpen] = React.useState(false);
|
|
171
248
|
/** `/` transcript search (less-style incsearch, ported from CC's REPL). */
|
|
@@ -216,7 +293,7 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
216
293
|
}
|
|
217
294
|
else {
|
|
218
295
|
exitPendingRef.current = true;
|
|
219
|
-
channel.notify('
|
|
296
|
+
channel.notify(t('exit-press-again'));
|
|
220
297
|
exitTimerRef.current = setTimeout(() => {
|
|
221
298
|
exitPendingRef.current = false;
|
|
222
299
|
}, 3000);
|
|
@@ -261,6 +338,66 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
261
338
|
? (TITLE_ANIMATION_FRAMES[titleFrame] ?? '✦')
|
|
262
339
|
: '✦';
|
|
263
340
|
useTerminalTitle(`${titlePrefix} 🐋 ${channel.sessionTitle}`);
|
|
341
|
+
const handleWorkspaceResult = (result) => {
|
|
342
|
+
workspaceFlowAbortRef.current = null;
|
|
343
|
+
setWorkspaceFlowBusy(false);
|
|
344
|
+
setWorkspaceFlowInput(null);
|
|
345
|
+
if (result.kind === 'target') {
|
|
346
|
+
setWorkspaceFlow(null);
|
|
347
|
+
void channel.switchWorkspace(result.target);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
if (result.choices.length === 0) {
|
|
351
|
+
setWorkspaceFlow(null);
|
|
352
|
+
channel.notify(t('workspace-command-empty'));
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
setWorkspaceFlow(result);
|
|
356
|
+
setWorkspaceFlowIndex(0);
|
|
357
|
+
};
|
|
358
|
+
const runWorkspaceFlowAction = (action) => {
|
|
359
|
+
const request = ++workspaceFlowRequestRef.current;
|
|
360
|
+
const controller = new AbortController();
|
|
361
|
+
workspaceFlowAbortRef.current = controller;
|
|
362
|
+
setWorkspaceFlowBusy(true);
|
|
363
|
+
void Promise.resolve()
|
|
364
|
+
.then(() => action(controller.signal))
|
|
365
|
+
.then((result) => {
|
|
366
|
+
if (request === workspaceFlowRequestRef.current)
|
|
367
|
+
handleWorkspaceResult(result);
|
|
368
|
+
})
|
|
369
|
+
.catch((error) => {
|
|
370
|
+
if (request !== workspaceFlowRequestRef.current)
|
|
371
|
+
return;
|
|
372
|
+
workspaceFlowAbortRef.current = null;
|
|
373
|
+
setWorkspaceFlowBusy(false);
|
|
374
|
+
channel.notify(t('workspace-command-failed', { err: error instanceof Error ? error.message : String(error) }), { color: 'error', timeoutMs: 8000 });
|
|
375
|
+
});
|
|
376
|
+
};
|
|
377
|
+
const openWorkspaceTarget = (reference) => {
|
|
378
|
+
void channel.resolveWorkspace(reference).then((target) => {
|
|
379
|
+
if (target === undefined) {
|
|
380
|
+
channel.notify(t('workspace-uri-invalid', { uri: reference }), { color: 'error', timeoutMs: 8000 });
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
void channel.switchWorkspace(target);
|
|
384
|
+
}).catch((error) => {
|
|
385
|
+
channel.notify(t('workspace-uri-failed', { err: error instanceof Error ? error.message : String(error) }), { color: 'error', timeoutMs: 8000 });
|
|
386
|
+
});
|
|
387
|
+
};
|
|
388
|
+
const openWorkspaceResume = () => {
|
|
389
|
+
void channel.listWorkspaces().then((targets) => {
|
|
390
|
+
if (targets.length === 0) {
|
|
391
|
+
channel.notify(t('workspace-none'));
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
setWorkspaceTargets(targets);
|
|
395
|
+
setWorkspaceIndex(Math.max(0, targets.findIndex(target => target.cwd === channel.cwd)));
|
|
396
|
+
setWorkspacePickerOpen(true);
|
|
397
|
+
}).catch((error) => {
|
|
398
|
+
channel.notify(t('workspace-list-failed', { err: error instanceof Error ? error.message : String(error) }), { color: 'error' });
|
|
399
|
+
});
|
|
400
|
+
};
|
|
264
401
|
/**
|
|
265
402
|
* Dispatch a slash command; false lets the input flow to the model.
|
|
266
403
|
* Built-in names run the local switch; anything registered by a DSH
|
|
@@ -275,7 +412,7 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
275
412
|
// opens the interactive indicator picker; `/activity frames <name>`
|
|
276
413
|
// switches directly; `/activity frames` lists presets; `/activity
|
|
277
414
|
// status` shows the current choice. The choice persists to
|
|
278
|
-
// ~/.dsh-
|
|
415
|
+
// ~/.dsh-tui/working-activity.json and survives restarts.
|
|
279
416
|
const parts = rawInput.trim().split(/\s+/).filter(Boolean);
|
|
280
417
|
if (parts[0] === 'status') {
|
|
281
418
|
setHelpOpen(false);
|
|
@@ -314,7 +451,7 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
314
451
|
// switches directly; `/preset status` shows the current choice. A
|
|
315
452
|
// blank session swaps composition in place (official blank-only
|
|
316
453
|
// rule); a started session is locked and the choice persists as the
|
|
317
|
-
// default for future sessions (~/.dsh-
|
|
454
|
+
// default for future sessions (~/.dsh-tui/agent-preset.json).
|
|
318
455
|
const parts = rawInput.trim().split(/\s+/).filter(Boolean);
|
|
319
456
|
if (parts[0] === 'status') {
|
|
320
457
|
setHelpOpen(false);
|
|
@@ -345,10 +482,42 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
345
482
|
});
|
|
346
483
|
return true;
|
|
347
484
|
}
|
|
485
|
+
case 'effort': {
|
|
486
|
+
// Bare `/effort` opens the rheostat slider over the live route's
|
|
487
|
+
// adapter levels (←/→ applies each step immediately); `/effort <id>`
|
|
488
|
+
// sets directly (validated by the channel); `/effort status` prints
|
|
489
|
+
// the current level. The choice persists to ~/.dsh-tui/effort.json.
|
|
490
|
+
const parts = rawInput.trim().split(/\s+/).filter(Boolean);
|
|
491
|
+
if (parts[0] === 'status') {
|
|
492
|
+
setHelpOpen(false);
|
|
493
|
+
channel.pushLocal('/effort', [
|
|
494
|
+
t('effort-current', { name: channel.reasoningEffort ?? '—' }),
|
|
495
|
+
t('effort-usage'),
|
|
496
|
+
]);
|
|
497
|
+
return true;
|
|
498
|
+
}
|
|
499
|
+
if (parts.length > 0) {
|
|
500
|
+
setHelpOpen(false);
|
|
501
|
+
void channel.setEffort(parts[0]);
|
|
502
|
+
return true;
|
|
503
|
+
}
|
|
504
|
+
setHelpOpen(false);
|
|
505
|
+
void channel.listEfforts().then(({ efforts, defaultEffort }) => {
|
|
506
|
+
// 0/1-tier routes were already notified by listEfforts.
|
|
507
|
+
if (efforts.length <= 1)
|
|
508
|
+
return;
|
|
509
|
+
setEffortOptions(efforts);
|
|
510
|
+
const current = channel.reasoningEffort ?? defaultEffort;
|
|
511
|
+
const index = efforts.findIndex(effort => effort.id === current);
|
|
512
|
+
setEffortIndex(index >= 0 ? index : 0);
|
|
513
|
+
setEffortSliderOpen(true);
|
|
514
|
+
});
|
|
515
|
+
return true;
|
|
516
|
+
}
|
|
348
517
|
case 'lang': {
|
|
349
518
|
// `/lang` shows the current UI language, `/lang en|zh` switches
|
|
350
|
-
// (hot-swap, persisted to ~/.dsh-
|
|
351
|
-
// launch:
|
|
519
|
+
// (hot-swap, persisted to ~/.dsh-tui/lang.json). Precedence on next
|
|
520
|
+
// launch: DSH_TUI_LANG > cordis.yml `lang` > the persisted choice.
|
|
352
521
|
const parts = rawInput.trim().split(/\s+/).filter(Boolean);
|
|
353
522
|
if (parts[0] === 'status') {
|
|
354
523
|
setHelpOpen(false);
|
|
@@ -380,16 +549,22 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
380
549
|
return true;
|
|
381
550
|
}
|
|
382
551
|
case 'theme': {
|
|
383
|
-
// Bare `/theme` opens the interactive color picker (built-in
|
|
384
|
-
// palettes + user themes from ~/.dsh-
|
|
552
|
+
// Bare `/theme` opens the interactive color picker (`auto` + built-in
|
|
553
|
+
// palettes + user themes from ~/.dsh-tui/themes); `/theme <name>`
|
|
385
554
|
// switches directly; `/theme status` shows the current choice.
|
|
386
|
-
//
|
|
387
|
-
//
|
|
555
|
+
// `auto` follows the terminal background (OSC 11). Selection
|
|
556
|
+
// persists to ~/.dsh-tui/theme.json and hot swaps via the
|
|
557
|
+
// ThemeProvider setter (DSH_TUI_THEME still wins on next launch).
|
|
388
558
|
const parts = rawInput.trim().split(/\s+/).filter(Boolean);
|
|
389
559
|
if (parts[0] === 'status') {
|
|
390
560
|
setHelpOpen(false);
|
|
391
561
|
channel.pushLocal('/theme', [
|
|
392
562
|
t('theme-current', { name: themeName }),
|
|
563
|
+
// `auto` resolves through terminal-background detection; show
|
|
564
|
+
// which palette it currently maps to.
|
|
565
|
+
...(themeName === AUTO_THEME_NAME
|
|
566
|
+
? [t('theme-auto-resolved', { name: getAutoThemeBase() })]
|
|
567
|
+
: []),
|
|
393
568
|
t('theme-switch-hint'),
|
|
394
569
|
t('theme-persist-hint'),
|
|
395
570
|
t('theme-custom-hint'),
|
|
@@ -414,7 +589,7 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
414
589
|
setHelpOpen(false);
|
|
415
590
|
void channel.newSession().then((ok) => {
|
|
416
591
|
if (ok)
|
|
417
|
-
channel.notify('
|
|
592
|
+
channel.notify(t('new-session-started'));
|
|
418
593
|
});
|
|
419
594
|
return true;
|
|
420
595
|
}
|
|
@@ -429,6 +604,12 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
429
604
|
case 'compact':
|
|
430
605
|
channel.compact();
|
|
431
606
|
return true;
|
|
607
|
+
case 'trace':
|
|
608
|
+
// `/trace` is kept as the discoverable spelling of Ctrl+T: the
|
|
609
|
+
// command menu is where a user finds out the trajectory exists.
|
|
610
|
+
setHelpOpen(false);
|
|
611
|
+
openScene();
|
|
612
|
+
return true;
|
|
432
613
|
case 'help':
|
|
433
614
|
setHelpOpen(true);
|
|
434
615
|
return true;
|
|
@@ -441,19 +622,42 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
441
622
|
setModelIndex(index >= 0 ? index : 0);
|
|
442
623
|
});
|
|
443
624
|
return true;
|
|
625
|
+
case 'provider': {
|
|
626
|
+
// Interactive add-provider wizard (/provider): drives the shared
|
|
627
|
+
// question panel, persists profile + key via the channel's settings/
|
|
628
|
+
// credentials seams. No picker state — AskUserQuestionPanel renders it.
|
|
629
|
+
setHelpOpen(false);
|
|
630
|
+
const host = channel.providerSetup();
|
|
631
|
+
if (!host) {
|
|
632
|
+
channel.notify(t('provider-unavailable'), { color: 'warning', timeoutMs: 8000 });
|
|
633
|
+
return true;
|
|
634
|
+
}
|
|
635
|
+
void runProviderWizard({
|
|
636
|
+
host,
|
|
637
|
+
ask: (request, options) => questionStore.ask(request, options),
|
|
638
|
+
notify: (text, options) => channel.notify(text, options),
|
|
639
|
+
pushLocal: (title, lines) => channel.pushLocal(title, lines),
|
|
640
|
+
working: () => channel.working,
|
|
641
|
+
switchModel: (provider, model) => channel.switchModel(provider, model),
|
|
642
|
+
}).catch(() => {
|
|
643
|
+
// The wizard notifies on every handled failure; this only swallows
|
|
644
|
+
// an unexpected reject so it never surfaces as an unhandled promise.
|
|
645
|
+
});
|
|
646
|
+
return true;
|
|
647
|
+
}
|
|
444
648
|
case 'thinking':
|
|
445
649
|
setHelpOpen(false);
|
|
446
650
|
setThinkingOpen(true);
|
|
447
651
|
setThinkingFocus(thinkingVisible ? 0 : 1);
|
|
448
652
|
return true;
|
|
449
653
|
case 'tokens': {
|
|
450
|
-
const usage =
|
|
654
|
+
const usage = t('tokens-usage', { in: formatTokens(channel.tokens.input), out: formatTokens(channel.tokens.output) });
|
|
451
655
|
if (channel.contextWindow === undefined) {
|
|
452
656
|
channel.notify(usage);
|
|
453
657
|
}
|
|
454
658
|
else {
|
|
455
659
|
const percent = Math.max(0, Math.min(100, Math.round((channel.tokens.input / channel.contextWindow) * 100)));
|
|
456
|
-
channel.notify(
|
|
660
|
+
channel.notify(t('tokens-usage-context', { usage, percent }));
|
|
457
661
|
}
|
|
458
662
|
return true;
|
|
459
663
|
}
|
|
@@ -471,11 +675,53 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
471
675
|
channel.notify(t('resume-none-in-cwd'));
|
|
472
676
|
return;
|
|
473
677
|
}
|
|
678
|
+
setResumeMode('list');
|
|
474
679
|
setResumePickerOpen(true);
|
|
475
680
|
setResumeIndex(0);
|
|
476
681
|
})();
|
|
477
682
|
return true;
|
|
478
683
|
}
|
|
684
|
+
case 'workspace': {
|
|
685
|
+
setHelpOpen(false);
|
|
686
|
+
const trimmed = rawInput.trim();
|
|
687
|
+
const separator = trimmed.search(/\s/u);
|
|
688
|
+
const subcommand = (separator < 0 ? trimmed : trimmed.slice(0, separator)).toLowerCase();
|
|
689
|
+
const input = separator < 0 ? '' : trimmed.slice(separator).trim();
|
|
690
|
+
if (subcommand === '') {
|
|
691
|
+
const extensions = channel.workspaceCommands()
|
|
692
|
+
.map(command => ` | ${command.name}`)
|
|
693
|
+
.join('');
|
|
694
|
+
channel.pushLocal('/workspace', [t('workspace-command-usage', { commands: extensions })]);
|
|
695
|
+
}
|
|
696
|
+
else if (subcommand === 'resume') {
|
|
697
|
+
openWorkspaceResume();
|
|
698
|
+
}
|
|
699
|
+
else if (subcommand === 'rename') {
|
|
700
|
+
if (input.length === 0)
|
|
701
|
+
channel.notify(t('workspace-rename-usage'));
|
|
702
|
+
else
|
|
703
|
+
void channel.renameWorkspace(input);
|
|
704
|
+
}
|
|
705
|
+
else if (subcommand === 'open') {
|
|
706
|
+
if (input.length === 0)
|
|
707
|
+
channel.notify(t('workspace-open-usage'));
|
|
708
|
+
else
|
|
709
|
+
openWorkspaceTarget(input);
|
|
710
|
+
}
|
|
711
|
+
else if (channel.workspaceCommands().some(command => command.name.toLowerCase() === subcommand
|
|
712
|
+
|| command.aliases?.some(alias => alias.toLowerCase() === subcommand))) {
|
|
713
|
+
void channel.runWorkspaceCommand(subcommand, input).then((result) => {
|
|
714
|
+
if (result !== undefined)
|
|
715
|
+
handleWorkspaceResult(result);
|
|
716
|
+
}).catch((error) => {
|
|
717
|
+
channel.notify(t('workspace-command-failed', { err: error instanceof Error ? error.message : String(error) }), { color: 'error', timeoutMs: 8000 });
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
else {
|
|
721
|
+
channel.notify(t('workspace-command-unknown', { command: subcommand }), { color: 'error' });
|
|
722
|
+
}
|
|
723
|
+
return true;
|
|
724
|
+
}
|
|
479
725
|
case 'rename': {
|
|
480
726
|
setHelpOpen(false);
|
|
481
727
|
const title = rawInput.trim();
|
|
@@ -497,6 +743,8 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
497
743
|
openRewind();
|
|
498
744
|
return true;
|
|
499
745
|
case 'exit':
|
|
746
|
+
case 'quit':
|
|
747
|
+
case 'q':
|
|
500
748
|
onExit();
|
|
501
749
|
return true;
|
|
502
750
|
case 'status': {
|
|
@@ -508,7 +756,7 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
508
756
|
`${t('status-model', { model: channel.model })}${channel.reasoningEffort ? ` · ${capitalize(channel.reasoningEffort)} effort` : ''}`,
|
|
509
757
|
`${t('status-state', { state: channel.working ? t('status-working') : t('status-idle') })}`,
|
|
510
758
|
`${t('status-session', { id: channel.agentId })}`,
|
|
511
|
-
`${t('status-dir', { cwd: channel.
|
|
759
|
+
`${t('status-dir', { cwd: channel.displayCwd })}${channel.gitBranch ? ` · ${channel.gitBranch}` : ''}`,
|
|
512
760
|
`Tokens ${formatTokens(channel.tokens.input)} in → ${formatTokens(channel.tokens.output)} out`,
|
|
513
761
|
];
|
|
514
762
|
if (usage !== undefined) {
|
|
@@ -598,6 +846,13 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
598
846
|
channel.pushLocal('/permissions', [
|
|
599
847
|
t('permissions-policy-hint'),
|
|
600
848
|
t('permissions-approval-hint'),
|
|
849
|
+
// /permission comes from the dsh-base permission-presets row via the
|
|
850
|
+
// commands registry; only advertise it when this composition
|
|
851
|
+
// actually mounted it (the bare cordis.yml leaf has no
|
|
852
|
+
// permission-presets, so the command does not exist there).
|
|
853
|
+
...(channel.commandList.some(command => command.name === 'permission')
|
|
854
|
+
? [t('permissions-preset-hint')]
|
|
855
|
+
: []),
|
|
601
856
|
]);
|
|
602
857
|
return true;
|
|
603
858
|
case 'add-dir':
|
|
@@ -648,6 +903,29 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
648
903
|
t('terminal-paste-hint', { mod: modLabel }),
|
|
649
904
|
]);
|
|
650
905
|
return true;
|
|
906
|
+
case 'btw': {
|
|
907
|
+
// CC /btw:单轮无工具侧问,overlay 态纯 UI,不打断主回合、不写
|
|
908
|
+
// 会话历史。空参数只提示用法。
|
|
909
|
+
setHelpOpen(false);
|
|
910
|
+
const question = rawInput.trim();
|
|
911
|
+
if (!question) {
|
|
912
|
+
channel.notify(t('btw-usage'), { timeoutMs: 3000 });
|
|
913
|
+
return true;
|
|
914
|
+
}
|
|
915
|
+
btwAbortRef.current?.abort();
|
|
916
|
+
const controller = new AbortController();
|
|
917
|
+
btwAbortRef.current = controller;
|
|
918
|
+
setBtw({ question, answer: '', done: false });
|
|
919
|
+
void channel.sideQuestion(question, {
|
|
920
|
+
signal: controller.signal,
|
|
921
|
+
onText: delta => setBtw(prev => (prev ? { ...prev, answer: prev.answer + delta } : prev)),
|
|
922
|
+
}).then(result => {
|
|
923
|
+
if (controller.signal.aborted)
|
|
924
|
+
return;
|
|
925
|
+
setBtw(prev => (prev ? { ...prev, answer: result.answer ?? prev.answer, error: result.error, done: true } : prev));
|
|
926
|
+
});
|
|
927
|
+
return true;
|
|
928
|
+
}
|
|
651
929
|
case 'connect':
|
|
652
930
|
setHelpOpen(false);
|
|
653
931
|
channel.pushLocal('/connect', [t('connect-none')]);
|
|
@@ -680,7 +958,7 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
680
958
|
channel.notify(text);
|
|
681
959
|
}
|
|
682
960
|
else if (text === undefined) {
|
|
683
|
-
channel.notify(
|
|
961
|
+
channel.notify(t('command-not-found', { name }), { color: 'error' });
|
|
684
962
|
}
|
|
685
963
|
});
|
|
686
964
|
return true;
|
|
@@ -721,7 +999,7 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
721
999
|
.filter(row => row.kind === 'user' && row.label === undefined)
|
|
722
1000
|
.reverse();
|
|
723
1001
|
if (candidates.length === 0) {
|
|
724
|
-
channel.notify('
|
|
1002
|
+
channel.notify(t('rewind-none'));
|
|
725
1003
|
return;
|
|
726
1004
|
}
|
|
727
1005
|
setRewindIndex(0);
|
|
@@ -734,9 +1012,74 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
734
1012
|
if (text !== null) {
|
|
735
1013
|
// CC puts the restored message back in the prompt for re-editing.
|
|
736
1014
|
setHistoryFill(text);
|
|
737
|
-
channel.notify('
|
|
1015
|
+
channel.notify(t('rewind-done'));
|
|
738
1016
|
}
|
|
739
1017
|
};
|
|
1018
|
+
/**
|
|
1019
|
+
* The session's trajectory projection, folded here rather than inside the
|
|
1020
|
+
* scene.
|
|
1021
|
+
*
|
|
1022
|
+
* Two things fall out of owning it at this level: the status-line chip can
|
|
1023
|
+
* show live counters without a second fold, and opening the scene is
|
|
1024
|
+
* instant because the build is already warm. The fold is incremental — it
|
|
1025
|
+
* consumes only events appended since the last render — so an idle
|
|
1026
|
+
* conversation pays nothing for it.
|
|
1027
|
+
*/
|
|
1028
|
+
const trajectoryRef = React.useRef(null);
|
|
1029
|
+
trajectoryRef.current = extendTrajectory(trajectoryRef.current,
|
|
1030
|
+
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: headless hosts render Chat with a partial channel
|
|
1031
|
+
channel.traceEvents?.() ?? NO_EVENTS);
|
|
1032
|
+
const trajectory = trajectoryRef.current;
|
|
1033
|
+
/**
|
|
1034
|
+
* The status-line wake.
|
|
1035
|
+
*
|
|
1036
|
+
* Projected onto a dozen-odd columns and memoized against the ledger's row
|
|
1037
|
+
* count, so it recomputes when the session actually grows rather than on
|
|
1038
|
+
* every animation tick. The tick only re-colours the cells it already has.
|
|
1039
|
+
*/
|
|
1040
|
+
const { columns: terminalColumns } = useTerminalSize();
|
|
1041
|
+
const wakeWidth = miniWakeWidth(terminalColumns);
|
|
1042
|
+
const wakeBand = React.useMemo(() => wakeWidth === 0
|
|
1043
|
+
? undefined
|
|
1044
|
+
// `sequence`, not the scene's `compressed`: at sixteen columns an idle
|
|
1045
|
+
// gap cannot express how long it was, so it only reads as a broken
|
|
1046
|
+
// strip. Equal-width columns give a continuous silhouette, which is
|
|
1047
|
+
// the only thing this size can actually say.
|
|
1048
|
+
// Width is also clamped to the row count: with fewer rows than
|
|
1049
|
+
// columns the strip would be mostly gaps, which reads as broken
|
|
1050
|
+
// rather than as short. It simply grows as the session does.
|
|
1051
|
+
: projectWave(trajectory.nodes, Math.min(wakeWidth, trajectory.nodes.length), 'sequence'),
|
|
1052
|
+
// The node array is mutated in place by the incremental fold, so its
|
|
1053
|
+
// length is the honest dependency; its identity never changes.
|
|
1054
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
1055
|
+
[trajectory.nodes, trajectory.counts.rows, wakeWidth]);
|
|
1056
|
+
const [wakeTickRef, wakeTime] = useAnimationFrame(channel.working ? 120 : null);
|
|
1057
|
+
/**
|
|
1058
|
+
* The key hint beside the strip retires itself once the trajectory has been
|
|
1059
|
+
* opened — teaching belongs in the first minute, not on every frame forever.
|
|
1060
|
+
*/
|
|
1061
|
+
const [trajectorySeen, setTrajectorySeen] = React.useState(() => trajectorySeenProp ?? readTrajectorySeen());
|
|
1062
|
+
/**
|
|
1063
|
+
* The one failure worth pointing at.
|
|
1064
|
+
*
|
|
1065
|
+
* Only the LATEST failed tool row carries the footnote, and only while its
|
|
1066
|
+
* failures are unseen. Repeating it under every historical failure would be
|
|
1067
|
+
* exactly the clutter the whole entry design is trying to avoid — one
|
|
1068
|
+
* pointer, at the newest problem, is enough to find the rest.
|
|
1069
|
+
*/
|
|
1070
|
+
const seenFailuresRef = React.useRef(0);
|
|
1071
|
+
const unreadFailures = Math.max(0, trajectory.counts.errors - seenFailuresRef.current);
|
|
1072
|
+
const failureHintRowId = React.useMemo(() => {
|
|
1073
|
+
if (unreadFailures === 0)
|
|
1074
|
+
return null;
|
|
1075
|
+
for (let index = channel.rows.length - 1; index >= 0; index--) {
|
|
1076
|
+
const row = channel.rows[index];
|
|
1077
|
+
if (row?.kind === 'tool' && row.tool?.status === 'error')
|
|
1078
|
+
return row.id;
|
|
1079
|
+
}
|
|
1080
|
+
return null;
|
|
1081
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
1082
|
+
}, [channel.rows, channel.version, unreadFailures]);
|
|
740
1083
|
// Row seeking under layout virtualization: a mounted row seeks directly;
|
|
741
1084
|
// an unmounted one is force-mounted first, then sought by the completion
|
|
742
1085
|
// effect below once its ref lands.
|
|
@@ -830,12 +1173,25 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
830
1173
|
else
|
|
831
1174
|
rowRefsRef.current.delete(rowId);
|
|
832
1175
|
}, []);
|
|
1176
|
+
/** Deduplicate terminals that report one Enter as parsed Return then raw CR/LF. */
|
|
1177
|
+
const lastModalEnterAtRef = React.useRef(0);
|
|
833
1178
|
useInput((input, key, event) => {
|
|
1179
|
+
// The /btw panel owns the keyboard while open (its own useInput handles
|
|
1180
|
+
// Esc/Enter/Space close, ↑/↓ scroll, c copy; everything else is
|
|
1181
|
+
// swallowed there). Chat registered first, so an early return here does
|
|
1182
|
+
// not block the event from reaching the panel.
|
|
1183
|
+
if (btw !== null)
|
|
1184
|
+
return;
|
|
834
1185
|
// The questionnaire / approval panel owns the keyboard while one is
|
|
835
1186
|
// pending (the panel's own useInput handles ↑/↓/Space/Tab/Enter/Esc;
|
|
836
1187
|
// the prompt input is unmounted, so nothing else should see these keys).
|
|
837
1188
|
if (questionSnapshot !== null || approvalSnapshot !== null)
|
|
838
1189
|
return;
|
|
1190
|
+
const returnCandidate = isPlainReturnInput(input, key);
|
|
1191
|
+
const returnNow = Date.now();
|
|
1192
|
+
const plainReturn = returnCandidate && returnNow - lastModalEnterAtRef.current >= 80;
|
|
1193
|
+
if (plainReturn)
|
|
1194
|
+
lastModalEnterAtRef.current = returnNow;
|
|
839
1195
|
// Mouse wheel scrolls the transcript — in fullscreen there is no
|
|
840
1196
|
// terminal scrollback (alt-screen), so this is the only way back.
|
|
841
1197
|
// Imperative scrollBy: no React re-render per notch (CC semantics).
|
|
@@ -862,7 +1218,7 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
862
1218
|
setHighlight('');
|
|
863
1219
|
handle?.scrollTo(searchAnchorRef.current);
|
|
864
1220
|
}
|
|
865
|
-
else if (
|
|
1221
|
+
else if (plainReturn) {
|
|
866
1222
|
// Enter commits; 0-match junk queries don't persist (CC behavior).
|
|
867
1223
|
if (searchCount === 0)
|
|
868
1224
|
setSearchQuery('');
|
|
@@ -915,12 +1271,12 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
915
1271
|
if (thinkingOpen) {
|
|
916
1272
|
if (thinkingConfirm !== null) {
|
|
917
1273
|
// Confirmation state: Enter applies, Esc backs out to the select.
|
|
918
|
-
if (
|
|
1274
|
+
if (plainReturn) {
|
|
919
1275
|
const enabled = thinkingConfirm;
|
|
920
1276
|
setThinkingVisible(enabled);
|
|
921
1277
|
setThinkingConfirm(null);
|
|
922
1278
|
setThinkingOpen(false);
|
|
923
|
-
channel.notify(
|
|
1279
|
+
channel.notify(t('thinking-toggled', { state: enabled ? t('thinking-on') : t('thinking-off') }));
|
|
924
1280
|
}
|
|
925
1281
|
else if (key.escape) {
|
|
926
1282
|
setThinkingConfirm(null);
|
|
@@ -929,7 +1285,7 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
929
1285
|
else if (key.upArrow || key.downArrow) {
|
|
930
1286
|
setThinkingFocus(index => (index === 0 ? 1 : 0));
|
|
931
1287
|
}
|
|
932
|
-
else if (
|
|
1288
|
+
else if (plainReturn) {
|
|
933
1289
|
const enabled = thinkingFocus === 0;
|
|
934
1290
|
const midConversation = channel.rows.some(row => row.kind === 'assistant');
|
|
935
1291
|
if (midConversation && enabled !== thinkingVisible) {
|
|
@@ -938,7 +1294,7 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
938
1294
|
else {
|
|
939
1295
|
setThinkingVisible(enabled);
|
|
940
1296
|
setThinkingOpen(false);
|
|
941
|
-
channel.notify(
|
|
1297
|
+
channel.notify(t('thinking-toggled', { state: enabled ? t('thinking-on') : t('thinking-off') }));
|
|
942
1298
|
}
|
|
943
1299
|
}
|
|
944
1300
|
else if (key.escape) {
|
|
@@ -946,25 +1302,210 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
946
1302
|
}
|
|
947
1303
|
return;
|
|
948
1304
|
}
|
|
1305
|
+
if (workspaceFlow !== null) {
|
|
1306
|
+
if (key.escape) {
|
|
1307
|
+
if (workspaceFlowInput !== null && !workspaceFlowBusy) {
|
|
1308
|
+
setWorkspaceFlowInput(null);
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
workspaceFlowAbortRef.current?.abort();
|
|
1312
|
+
workspaceFlowAbortRef.current = null;
|
|
1313
|
+
workspaceFlowRequestRef.current += 1;
|
|
1314
|
+
setWorkspaceFlowBusy(false);
|
|
1315
|
+
setWorkspaceFlow(null);
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
if (workspaceFlowBusy)
|
|
1319
|
+
return;
|
|
1320
|
+
if (workspaceFlowInput !== null) {
|
|
1321
|
+
const choice = workspaceFlow.choices.find(candidate => candidate.id === workspaceFlowInput.choiceId);
|
|
1322
|
+
const editor = choice?.input;
|
|
1323
|
+
if (plainReturn) {
|
|
1324
|
+
const value = workspaceFlowInput.value.trim();
|
|
1325
|
+
if (value.length === 0) {
|
|
1326
|
+
channel.notify(t('workspace-flow-input-empty'), { color: 'warning' });
|
|
1327
|
+
}
|
|
1328
|
+
else if (editor !== undefined) {
|
|
1329
|
+
runWorkspaceFlowAction(signal => editor.submit(value, signal));
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
else if (key.backspace && workspaceFlowInput.cursor > 0) {
|
|
1333
|
+
setWorkspaceFlowInput(current => current === null ? null : {
|
|
1334
|
+
...current,
|
|
1335
|
+
value: current.value.slice(0, current.cursor - 1) + current.value.slice(current.cursor),
|
|
1336
|
+
cursor: current.cursor - 1,
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
else if (key.delete && workspaceFlowInput.cursor < workspaceFlowInput.value.length) {
|
|
1340
|
+
setWorkspaceFlowInput(current => current === null ? null : {
|
|
1341
|
+
...current,
|
|
1342
|
+
value: current.value.slice(0, current.cursor) + current.value.slice(current.cursor + 1),
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
else if (key.leftArrow) {
|
|
1346
|
+
setWorkspaceFlowInput(current => current === null ? null : {
|
|
1347
|
+
...current,
|
|
1348
|
+
cursor: Math.max(0, current.cursor - 1),
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
else if (key.rightArrow) {
|
|
1352
|
+
setWorkspaceFlowInput(current => current === null ? null : {
|
|
1353
|
+
...current,
|
|
1354
|
+
cursor: Math.min(current.value.length, current.cursor + 1),
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
else if (input.length > 0 && !key.ctrl && !key.meta && !key.super && !key.tab) {
|
|
1358
|
+
setWorkspaceFlowInput(current => current === null ? null : {
|
|
1359
|
+
...current,
|
|
1360
|
+
value: current.value.slice(0, current.cursor) + input + current.value.slice(current.cursor),
|
|
1361
|
+
cursor: current.cursor + input.length,
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
if (key.upArrow) {
|
|
1367
|
+
setWorkspaceFlowIndex(index => (index <= 0 ? workspaceFlow.choices.length - 1 : index - 1));
|
|
1368
|
+
}
|
|
1369
|
+
else if (key.downArrow) {
|
|
1370
|
+
setWorkspaceFlowIndex(index => (index >= workspaceFlow.choices.length - 1 ? 0 : index + 1));
|
|
1371
|
+
}
|
|
1372
|
+
else if (key.tab && !key.shift) {
|
|
1373
|
+
const choice = workspaceFlow.choices[workspaceFlowIndex];
|
|
1374
|
+
if (choice?.input !== undefined) {
|
|
1375
|
+
const value = choice.input.initialValue ?? '';
|
|
1376
|
+
setWorkspaceFlowInput({
|
|
1377
|
+
choiceId: choice.id,
|
|
1378
|
+
value,
|
|
1379
|
+
cursor: value.length,
|
|
1380
|
+
...(choice.input.placeholder === undefined ? {} : { placeholder: choice.input.placeholder }),
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
else if (plainReturn) {
|
|
1385
|
+
const choice = workspaceFlow.choices[workspaceFlowIndex];
|
|
1386
|
+
if (choice !== undefined) {
|
|
1387
|
+
runWorkspaceFlowAction(signal => choice.choose(signal));
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
if (workspacePickerOpen) {
|
|
1393
|
+
if (key.upArrow) {
|
|
1394
|
+
setWorkspaceIndex(index => (index <= 0 ? workspaceTargets.length - 1 : index - 1));
|
|
1395
|
+
}
|
|
1396
|
+
else if (key.downArrow) {
|
|
1397
|
+
setWorkspaceIndex(index => (index >= workspaceTargets.length - 1 ? 0 : index + 1));
|
|
1398
|
+
}
|
|
1399
|
+
else if (plainReturn) {
|
|
1400
|
+
const target = workspaceTargets[workspaceIndex];
|
|
1401
|
+
setWorkspacePickerOpen(false);
|
|
1402
|
+
if (target !== undefined)
|
|
1403
|
+
void channel.switchWorkspace(target);
|
|
1404
|
+
}
|
|
1405
|
+
else if (key.escape) {
|
|
1406
|
+
setWorkspacePickerOpen(false);
|
|
1407
|
+
}
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
949
1410
|
if (resumePickerOpen) {
|
|
1411
|
+
const resumeSession = resumeSessions[resumeIndex];
|
|
1412
|
+
// Session management modes (issue #112): the list keys stay untouched
|
|
1413
|
+
// until ctrl+d/ctrl+r switch into a sub-mode, each with Enter/Esc.
|
|
1414
|
+
if (resumeMode === 'confirm-delete') {
|
|
1415
|
+
if (plainReturn) {
|
|
1416
|
+
setResumeMode('list');
|
|
1417
|
+
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty list
|
|
1418
|
+
if (resumeSession) {
|
|
1419
|
+
const target = resumeSession;
|
|
1420
|
+
void (async () => {
|
|
1421
|
+
const ok = await channel.deleteSession(target.id);
|
|
1422
|
+
if (!ok) {
|
|
1423
|
+
channel.notify(t('resume-delete-failed', { name: target.title || target.id }), { color: 'error' });
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
channel.notify(t('resume-deleted', { name: target.title || target.id }));
|
|
1427
|
+
// Refresh right away so the row disappears in place; closing
|
|
1428
|
+
// the picker when nothing resumable remains.
|
|
1429
|
+
const sessions = await channel.listSessions();
|
|
1430
|
+
const pickable = sessions.filter(session => session.id !== channel.agentId);
|
|
1431
|
+
setResumeSessions(pickable);
|
|
1432
|
+
if (pickable.length === 0) {
|
|
1433
|
+
setResumePickerOpen(false);
|
|
1434
|
+
}
|
|
1435
|
+
else {
|
|
1436
|
+
setResumeIndex(index => Math.min(index, pickable.length - 1));
|
|
1437
|
+
}
|
|
1438
|
+
})();
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
else if (key.escape) {
|
|
1442
|
+
setResumeMode('list');
|
|
1443
|
+
}
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
if (resumeMode === 'rename') {
|
|
1447
|
+
if (plainReturn) {
|
|
1448
|
+
setResumeMode('list');
|
|
1449
|
+
const title = resumeRenameText.trim();
|
|
1450
|
+
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty list
|
|
1451
|
+
if (resumeSession && title.length > 0) {
|
|
1452
|
+
const target = resumeSession;
|
|
1453
|
+
void (async () => {
|
|
1454
|
+
const ok = await channel.renameSessionTo(target.id, title);
|
|
1455
|
+
if (!ok) {
|
|
1456
|
+
channel.notify(t('resume-rename-failed', { name: target.title || target.id }), { color: 'error' });
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1459
|
+
channel.notify(t('rename-done', { title }));
|
|
1460
|
+
// Re-list so the row reflects the persisted state, but patch
|
|
1461
|
+
// the renamed row's title explicitly: listSessions resolves
|
|
1462
|
+
// persisted titles only within the MRU top SESSION_TITLE_DEPTH
|
|
1463
|
+
// window, and a freshly renamed row must never snap back to
|
|
1464
|
+
// the basename fallback in between.
|
|
1465
|
+
const sessions = await channel.listSessions();
|
|
1466
|
+
const next = sessions
|
|
1467
|
+
.filter(session => session.id !== channel.agentId)
|
|
1468
|
+
.map(session => (session.id === target.id ? { ...session, title } : session));
|
|
1469
|
+
setResumeSessions(next);
|
|
1470
|
+
// Re-anchor focus on the renamed row: renameSessionTo touches
|
|
1471
|
+
// MRU, so the re-listed order shifts — a kept index would
|
|
1472
|
+
// silently point at a DIFFERENT session, and a following
|
|
1473
|
+
// Enter/ctrl+d would act on the wrong one (review leftover).
|
|
1474
|
+
const anchored = next.findIndex(session => session.id === target.id);
|
|
1475
|
+
if (anchored >= 0)
|
|
1476
|
+
setResumeIndex(anchored);
|
|
1477
|
+
})();
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
else if (key.escape) {
|
|
1481
|
+
setResumeMode('list');
|
|
1482
|
+
}
|
|
1483
|
+
else if (key.backspace) {
|
|
1484
|
+
setResumeRenameText(text => text.slice(0, -1));
|
|
1485
|
+
}
|
|
1486
|
+
else if (!key.ctrl && !key.meta && !key.super && input) {
|
|
1487
|
+
// Single-line title: pasted newlines collapse to spaces.
|
|
1488
|
+
setResumeRenameText(text => text + input.replace(/[\r\n]+/g, ' '));
|
|
1489
|
+
}
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
950
1492
|
if (key.upArrow) {
|
|
951
1493
|
setResumeIndex(index => (index <= 0 ? resumeSessions.length - 1 : index - 1));
|
|
952
1494
|
}
|
|
953
1495
|
else if (key.downArrow) {
|
|
954
1496
|
setResumeIndex(index => (index >= resumeSessions.length - 1 ? 0 : index + 1));
|
|
955
1497
|
}
|
|
956
|
-
else if (
|
|
957
|
-
const session = resumeSessions[resumeIndex];
|
|
1498
|
+
else if (plainReturn) {
|
|
958
1499
|
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty list
|
|
959
|
-
if (
|
|
1500
|
+
if (resumeSession) {
|
|
960
1501
|
// Enter switches the live agent to the persisted session right
|
|
961
1502
|
// away (the history replays into the transcript); the resume.txt
|
|
962
1503
|
// launcher marker is refreshed by resumeTo so `--resume` on the
|
|
963
1504
|
// next launch opens the same session.
|
|
964
1505
|
setResumePickerOpen(false);
|
|
965
|
-
void channel.resumeTo(
|
|
1506
|
+
void channel.resumeTo(resumeSession.id).then((ok) => {
|
|
966
1507
|
if (ok)
|
|
967
|
-
channel.notify('
|
|
1508
|
+
channel.notify(t('resume-resumed'));
|
|
968
1509
|
});
|
|
969
1510
|
}
|
|
970
1511
|
else {
|
|
@@ -974,6 +1515,13 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
974
1515
|
else if (key.escape) {
|
|
975
1516
|
setResumePickerOpen(false);
|
|
976
1517
|
}
|
|
1518
|
+
else if (isMod(key) && input === 'd' && resumeSession) {
|
|
1519
|
+
setResumeMode('confirm-delete');
|
|
1520
|
+
}
|
|
1521
|
+
else if (isMod(key) && input === 'r' && resumeSession) {
|
|
1522
|
+
setResumeRenameText(resumeSession.title || '');
|
|
1523
|
+
setResumeMode('rename');
|
|
1524
|
+
}
|
|
977
1525
|
return;
|
|
978
1526
|
}
|
|
979
1527
|
if (modelPickerOpen) {
|
|
@@ -983,7 +1531,7 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
983
1531
|
else if (key.downArrow) {
|
|
984
1532
|
setModelIndex(index => (index >= models.length - 1 ? 0 : index + 1));
|
|
985
1533
|
}
|
|
986
|
-
else if (
|
|
1534
|
+
else if (plainReturn) {
|
|
987
1535
|
const model = models[modelIndex];
|
|
988
1536
|
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty list
|
|
989
1537
|
if (model) {
|
|
@@ -991,10 +1539,10 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
991
1539
|
// forked at its end and continued with an agent routed to the new
|
|
992
1540
|
// model (history replays unchanged).
|
|
993
1541
|
setModelPickerOpen(false);
|
|
994
|
-
channel.notify(
|
|
1542
|
+
channel.notify(t('model-switching', { name: model.name }));
|
|
995
1543
|
void channel.switchModel(model.provider, model.id).then((ok) => {
|
|
996
1544
|
if (ok)
|
|
997
|
-
channel.notify(
|
|
1545
|
+
channel.notify(t('model-switched', { name: model.name }));
|
|
998
1546
|
});
|
|
999
1547
|
}
|
|
1000
1548
|
else {
|
|
@@ -1013,7 +1561,7 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
1013
1561
|
else if (key.downArrow) {
|
|
1014
1562
|
setActivityIndex(index => (index >= PRESET_NAMES.length - 1 ? 0 : index + 1));
|
|
1015
1563
|
}
|
|
1016
|
-
else if (
|
|
1564
|
+
else if (plainReturn) {
|
|
1017
1565
|
const name = PRESET_NAMES[activityIndex];
|
|
1018
1566
|
setActivityPickerOpen(false);
|
|
1019
1567
|
if (name)
|
|
@@ -1024,6 +1572,21 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
1024
1572
|
}
|
|
1025
1573
|
return;
|
|
1026
1574
|
}
|
|
1575
|
+
if (effortSliderOpen) {
|
|
1576
|
+
if (key.leftArrow || key.rightArrow) {
|
|
1577
|
+
const delta = key.leftArrow ? -1 : 1;
|
|
1578
|
+
const next = (effortIndex + delta + effortOptions.length) % effortOptions.length;
|
|
1579
|
+
setEffortIndex(next);
|
|
1580
|
+
const option = effortOptions[next];
|
|
1581
|
+
// Live-apply: the slider IS the control; Esc does not revert.
|
|
1582
|
+
if (option)
|
|
1583
|
+
void channel.setEffort(option.id);
|
|
1584
|
+
}
|
|
1585
|
+
else if (plainReturn || key.escape) {
|
|
1586
|
+
setEffortSliderOpen(false);
|
|
1587
|
+
}
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1027
1590
|
if (presetPickerOpen) {
|
|
1028
1591
|
if (key.upArrow) {
|
|
1029
1592
|
setPresetIndex(index => (index <= 0 ? presetOptions.length - 1 : index - 1));
|
|
@@ -1031,7 +1594,7 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
1031
1594
|
else if (key.downArrow) {
|
|
1032
1595
|
setPresetIndex(index => (index >= presetOptions.length - 1 ? 0 : index + 1));
|
|
1033
1596
|
}
|
|
1034
|
-
else if (
|
|
1597
|
+
else if (plainReturn) {
|
|
1035
1598
|
const option = presetOptions[presetIndex];
|
|
1036
1599
|
setPresetPickerOpen(false);
|
|
1037
1600
|
if (option)
|
|
@@ -1050,7 +1613,7 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
1050
1613
|
else if (key.downArrow) {
|
|
1051
1614
|
setThemeIndex(index => (index >= options.length - 1 ? 0 : index + 1));
|
|
1052
1615
|
}
|
|
1053
|
-
else if (
|
|
1616
|
+
else if (plainReturn) {
|
|
1054
1617
|
setThemePickerOpen(false);
|
|
1055
1618
|
const name = options[themeIndex]?.value;
|
|
1056
1619
|
if (name !== undefined) {
|
|
@@ -1071,7 +1634,7 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
1071
1634
|
// CC's history search cancels on ctrl+c/ctrl+d too.
|
|
1072
1635
|
setHistoryOpen(false);
|
|
1073
1636
|
}
|
|
1074
|
-
else if (
|
|
1637
|
+
else if (plainReturn) {
|
|
1075
1638
|
const entry = historyMatches[historyFocus];
|
|
1076
1639
|
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty match list
|
|
1077
1640
|
if (entry) {
|
|
@@ -1123,7 +1686,7 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
1123
1686
|
if (rewindOpen) {
|
|
1124
1687
|
if (rewindConfirm !== null) {
|
|
1125
1688
|
// Confirmation state: Enter rewinds, Esc backs out to the list.
|
|
1126
|
-
if (
|
|
1689
|
+
if (plainReturn) {
|
|
1127
1690
|
const row = rewindConfirm;
|
|
1128
1691
|
setRewindOpen(false);
|
|
1129
1692
|
setRewindConfirm(null);
|
|
@@ -1139,7 +1702,7 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
1139
1702
|
else if (key.downArrow) {
|
|
1140
1703
|
setRewindIndex(index => (index >= rewindRows.length - 1 ? 0 : index + 1));
|
|
1141
1704
|
}
|
|
1142
|
-
else if (
|
|
1705
|
+
else if (plainReturn) {
|
|
1143
1706
|
const row = rewindRows[rewindIndex];
|
|
1144
1707
|
// oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty list
|
|
1145
1708
|
if (row)
|
|
@@ -1152,9 +1715,13 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
1152
1715
|
return;
|
|
1153
1716
|
}
|
|
1154
1717
|
if (isMod(key) && input === 't') {
|
|
1155
|
-
//
|
|
1156
|
-
//
|
|
1157
|
-
|
|
1718
|
+
// Ctrl+T opens the trajectory scene (issue #80 evolution). It used to
|
|
1719
|
+
// toggle the startup loaded-context panel — which only ever renders
|
|
1720
|
+
// before the first message (see the render below), so the binding was
|
|
1721
|
+
// dead for the whole rest of a session. The panel keeps its header
|
|
1722
|
+
// click-to-toggle; the key now has a meaning that always applies.
|
|
1723
|
+
openScene();
|
|
1724
|
+
return;
|
|
1158
1725
|
}
|
|
1159
1726
|
if (isMod(key) && input === 'r' && !helpOpen) {
|
|
1160
1727
|
setHistoryQuery('');
|
|
@@ -1174,7 +1741,7 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
1174
1741
|
else if (key.downArrow) {
|
|
1175
1742
|
moveSelection(1);
|
|
1176
1743
|
}
|
|
1177
|
-
else if (
|
|
1744
|
+
else if (plainReturn && selectedId !== null) {
|
|
1178
1745
|
toggleRowExpanded(selectedId);
|
|
1179
1746
|
}
|
|
1180
1747
|
else if (key.escape) {
|
|
@@ -1241,7 +1808,7 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
1241
1808
|
else if (isMod(key) && input === 'e') {
|
|
1242
1809
|
setShowAllMessages(previous => !previous);
|
|
1243
1810
|
}
|
|
1244
|
-
else if (
|
|
1811
|
+
else if (plainReturn && showPill) {
|
|
1245
1812
|
handle?.scrollToBottom();
|
|
1246
1813
|
}
|
|
1247
1814
|
});
|
|
@@ -1249,24 +1816,57 @@ export function Chat({ channel, questionStore, approvalStore, onExit, onUpdate,
|
|
|
1249
1816
|
// StatusLine thresholds (amber ≥ 80, red ≥ 95).
|
|
1250
1817
|
const activityWarnPct = contextPressurePct(channel.lastUsage, channel.contextWindow);
|
|
1251
1818
|
/** Prompt input is inert while a modal dialog owns the keyboard. */
|
|
1252
|
-
const promptSelectionActive = selectionActive || modelPickerOpen || resumePickerOpen || activityPickerOpen ||
|
|
1253
|
-
presetPickerOpen || themePickerOpen || thinkingOpen || historyOpen || rewindOpen || searchOpen
|
|
1254
|
-
|
|
1819
|
+
const promptSelectionActive = selectionActive || modelPickerOpen || resumePickerOpen || workspacePickerOpen || workspaceFlow !== null || activityPickerOpen ||
|
|
1820
|
+
effortSliderOpen || presetPickerOpen || themePickerOpen || thinkingOpen || historyOpen || rewindOpen || searchOpen ||
|
|
1821
|
+
btw !== null;
|
|
1822
|
+
// The trajectory scene replaces the conversation for as long as it is open.
|
|
1823
|
+
// Rendering it INSTEAD of (not above) the transcript is what makes it a
|
|
1824
|
+
// screen rather than an overlay: it owns the full viewport, and the
|
|
1825
|
+
// conversation's own frame is never resized while it is up. Chat stays
|
|
1826
|
+
// mounted, so every hook above has already run and no state is lost.
|
|
1827
|
+
// `<AlternateScreen>` is skipped when the app is already fullscreen —
|
|
1828
|
+
// nesting it would emit a second DEC 1049, and its unmount would drop the
|
|
1829
|
+
// whole app back to the main screen.
|
|
1830
|
+
if (sceneOpen) {
|
|
1831
|
+
const scene = _jsx(TrajectoryScene, { channel: channel, build: trajectory, onClose: closeScene });
|
|
1832
|
+
return fullscreen ? scene : _jsx(AlternateScreen, { children: scene });
|
|
1833
|
+
}
|
|
1834
|
+
// 浮层整体挂载条件:必须与内部各面板的可见条件精确同值。关闭时把
|
|
1835
|
+
// 整个 absolute 浮层从树里移除——渲染器的"移除 absolute 节点"检测只看
|
|
1836
|
+
// 被移除子树自身的 style.position(dom.ts collectRemovedRects),若浮层
|
|
1837
|
+
// 常驻、只移除其普通子节点,blit 解毒不触发,被覆盖的转录行会在
|
|
1838
|
+
// blit-skip 后留空(Esc 关 picker 一片空白的根因)。
|
|
1839
|
+
const dialogOverlayOpen = thinkingOpen || (workspacePickerOpen && workspaceTargets.length > 0) || workspaceFlow !== null ||
|
|
1840
|
+
(resumePickerOpen && resumeSessions.length > 0) || modelPickerOpen ||
|
|
1841
|
+
activityPickerOpen || (effortSliderOpen && effortOptions.length > 1) ||
|
|
1842
|
+
(presetPickerOpen && presetOptions.length > 0) || themePickerOpen || historyOpen ||
|
|
1843
|
+
rewindOpen || searchOpen;
|
|
1844
|
+
return (_jsxs(Box, { ref: wakeTickRef, flexDirection: "column", flexGrow: 1, width: "100%", children: [!isSticky && channel.lastUserText && (_jsx(StickyPromptHeader, { text: channel.lastUserText, onClick: () => {
|
|
1255
1845
|
// Click jumps back to the pinned prompt (CC's StickyPromptHeader).
|
|
1256
1846
|
const lastUser = [...channel.rows].reverse().find(row => row.kind === 'user');
|
|
1257
1847
|
if (lastUser)
|
|
1258
1848
|
seekRow(lastUser.id);
|
|
1259
1849
|
else
|
|
1260
1850
|
handle?.scrollToBottom();
|
|
1261
|
-
} })), _jsxs(ScrollBox, { ref: setHandle, flexDirection: "column", flexGrow: 1, flexShrink: 1, stickyScroll: true, children: [_jsx(LogoHeader, { model: channel.model, effort: channel.reasoningEffort, cwd: channel.
|
|
1851
|
+
} })), _jsxs(ScrollBox, { ref: setHandle, flexDirection: "column", flexGrow: 1, flexShrink: 1, stickyScroll: true, children: [_jsx(LogoHeader, { model: channel.model, effort: channel.reasoningEffort, cwd: channel.displayCwd }), channel.rows.length === 0 && channel.loadedContext !== undefined && (_jsx(LoadedContextPanel, { context: channel.loadedContext, open: loadedContextOpen, onToggle: () => { setLoadedContextOpen(previous => !previous); } })), _jsx(MessageList, { rows: channel.rows, failureHintRowId: failureHintRowId, failureHint: t('traj-hint-failure', { key: `${modLabel}t` }), 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 &&
|
|
1262
1852
|
(channel.activityEnabled &&
|
|
1263
1853
|
channel.workingActivity !== undefined &&
|
|
1264
1854
|
channel.workingActivity.line !== '' &&
|
|
1265
|
-
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 }))),
|
|
1855
|
+
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 }))), _jsx(GoalTodoPanel, { channel: channel }), approvalSnapshot !== null ? (_jsx(ApprovalPanel, { approval: approvalSnapshot, onDecide: outcome => approvals.decide(outcome) }, approvalSnapshot.key)) : btw !== null ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(BtwPanel, { question: btw.question, answer: btw.answer, error: btw.error, streaming: !btw.done, onClose: closeBtw, onCopy: () => {
|
|
1856
|
+
void setClipboard(btw.answer ?? '').then(raw => { if (raw)
|
|
1857
|
+
process.stdout.write(raw); });
|
|
1858
|
+
channel.notify(t('copied-chars', { n: (btw.answer ?? '').length }), { timeoutMs: 1500 });
|
|
1859
|
+
} }) })) : 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)) : (_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, wake: wakeBand === undefined
|
|
1860
|
+
? undefined
|
|
1861
|
+
: {
|
|
1862
|
+
band: wakeBand,
|
|
1863
|
+
hint: trajectorySeen ? undefined : `${modLabel}t`,
|
|
1864
|
+
tick: Math.floor(wakeTime / 120),
|
|
1865
|
+
} }), dialogOverlayOpen && (_jsxs(OverlayAbove, { maxHeight: Math.max(terminalRows - 8, 8), children: [thinkingOpen && (_jsx(ThinkingToggle, { currentValue: thinkingVisible, focusIndex: thinkingFocus, confirmationPending: thinkingConfirm })), workspacePickerOpen && workspaceTargets.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(WorkspacePicker, { targets: workspaceTargets, focusIndex: workspaceIndex, currentCwd: channel.cwd }) })), workspaceFlow !== null && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(WorkspaceFlowPicker, { title: workspaceFlow.title, choices: workspaceFlow.choices, focusIndex: workspaceFlowIndex, busy: workspaceFlowBusy, input: workspaceFlowInput }) })), resumePickerOpen && resumeSessions.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ResumePicker, { sessions: resumeSessions, focusIndex: resumeIndex, currentSessionId: channel.agentId, mode: resumeMode, renameText: resumeRenameText }) })), 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 }) })), effortSliderOpen && effortOptions.length > 1 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(EffortSlider, { options: effortOptions, focusIndex: effortIndex, currentId: channel.reasoningEffort }) })), 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 })] }))] })] }));
|
|
1266
1866
|
}
|
|
1267
1867
|
/**
|
|
1268
1868
|
* The pinned prompt header shown above the ScrollBox while the user has
|
|
1269
|
-
* scrolled up (
|
|
1869
|
+
* scrolled up (mirroring Claude Code's FullscreenLayout.StickyPromptHeader).
|
|
1270
1870
|
* Fixed at 1 row so the ScrollBox never shifts when the text changes.
|
|
1271
1871
|
*/
|
|
1272
1872
|
function StickyPromptHeader({ text, onClick, }) {
|
|
@@ -1276,11 +1876,11 @@ function StickyPromptHeader({ text, onClick, }) {
|
|
|
1276
1876
|
/** The `↓ N new messages` pill shown while scrolled up with new content. */
|
|
1277
1877
|
function NewMessagesPill({ count, onClick, }) {
|
|
1278
1878
|
const [hover, setHover] = React.useState(false);
|
|
1279
|
-
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 ? '
|
|
1879
|
+
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 ", t(count === 1 ? 'new-message' : 'new-messages', { n: count }), ' '] }) }) }));
|
|
1280
1880
|
}
|
|
1281
1881
|
/** /model while the provider catalog is still loading (CC's LoadingState). */
|
|
1282
1882
|
function ModelPickerLoading() {
|
|
1283
|
-
return (_jsx(Pane, { color: "permission", children: _jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Text, { bold: true, color: "permission", children:
|
|
1883
|
+
return (_jsx(Pane, { color: "permission", children: _jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Text, { bold: true, color: "permission", children: t('picker-title-model') }), _jsx(LoadingState, { message: t('model-loading'), bold: true, subtitle: t('model-loading-subtitle') })] }) }));
|
|
1284
1884
|
}
|
|
1285
1885
|
/**
|
|
1286
1886
|
* The `/` incsearch bar (ported from CC's REPL TranscriptSearchBar): a
|
|
@@ -1288,5 +1888,5 @@ function ModelPickerLoading() {
|
|
|
1288
1888
|
* match counter (`current/count`) or a red `no matches` when nothing hits.
|
|
1289
1889
|
*/ function TranscriptSearchBar({ query, cursorOffset, count, current, }) {
|
|
1290
1890
|
const cursorChar = cursorOffset < query.length ? query[cursorOffset] : ' ';
|
|
1291
|
-
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 ? (
|
|
1891
|
+
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 ? (_jsxs(Text, { color: "error", children: [t('search-no-matches'), " "] })) : count > 0 ? (_jsxs(Text, { dimColor: true, children: [Math.min(current + 1, count), "/", count, ' '] })) : null] }));
|
|
1292
1892
|
}
|