@nexus-cortex/tui 4.47.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 +202 -0
- package/NOTICE +2 -0
- package/README.md +13 -0
- package/bin/cli-full.js +23 -0
- package/bin/cortex-ui.js +26 -0
- package/bin/ink-ui.js +108 -0
- package/bin/launcher-dev.js +266 -0
- package/bin/launcher.js +516 -0
- package/dist/commands/agent/InteractiveAgentMenu.d.ts +100 -0
- package/dist/commands/agent/InteractiveAgentMenu.d.ts.map +1 -0
- package/dist/commands/agent/InteractiveAgentMenu.js +909 -0
- package/dist/commands/agent/InteractiveAgentMenu.js.map +1 -0
- package/dist/commands/agent/index.d.ts +55 -0
- package/dist/commands/agent/index.d.ts.map +1 -0
- package/dist/commands/agent/index.js +714 -0
- package/dist/commands/agent/index.js.map +1 -0
- package/dist/commands/chat/interactive.d.ts +13 -0
- package/dist/commands/chat/interactive.d.ts.map +1 -0
- package/dist/commands/chat/interactive.js +2055 -0
- package/dist/commands/chat/interactive.js.map +1 -0
- package/dist/commands/system-message/EditorLauncher.d.ts +65 -0
- package/dist/commands/system-message/EditorLauncher.d.ts.map +1 -0
- package/dist/commands/system-message/EditorLauncher.js +143 -0
- package/dist/commands/system-message/EditorLauncher.js.map +1 -0
- package/dist/commands/system-message/InlineEditor.d.ts +23 -0
- package/dist/commands/system-message/InlineEditor.d.ts.map +1 -0
- package/dist/commands/system-message/InlineEditor.js +225 -0
- package/dist/commands/system-message/InlineEditor.js.map +1 -0
- package/dist/commands/system-message/InteractiveMenu.d.ts +76 -0
- package/dist/commands/system-message/InteractiveMenu.d.ts.map +1 -0
- package/dist/commands/system-message/InteractiveMenu.js +979 -0
- package/dist/commands/system-message/InteractiveMenu.js.map +1 -0
- package/dist/commands/system-message/index.d.ts +56 -0
- package/dist/commands/system-message/index.d.ts.map +1 -0
- package/dist/commands/system-message/index.js +694 -0
- package/dist/commands/system-message/index.js.map +1 -0
- package/dist/commands/ui/artifacts.d.ts +14 -0
- package/dist/commands/ui/artifacts.d.ts.map +1 -0
- package/dist/commands/ui/artifacts.js +22 -0
- package/dist/commands/ui/artifacts.js.map +1 -0
- package/dist/commands/ui/config.d.ts +12 -0
- package/dist/commands/ui/config.d.ts.map +1 -0
- package/dist/commands/ui/config.js +26 -0
- package/dist/commands/ui/config.js.map +1 -0
- package/dist/commands/ui/context.d.ts +16 -0
- package/dist/commands/ui/context.d.ts.map +1 -0
- package/dist/commands/ui/context.js +28 -0
- package/dist/commands/ui/context.js.map +1 -0
- package/dist/commands/ui/middleware.d.ts +14 -0
- package/dist/commands/ui/middleware.d.ts.map +1 -0
- package/dist/commands/ui/middleware.js +22 -0
- package/dist/commands/ui/middleware.js.map +1 -0
- package/dist/commands/ui/models.d.ts +14 -0
- package/dist/commands/ui/models.d.ts.map +1 -0
- package/dist/commands/ui/models.js +50 -0
- package/dist/commands/ui/models.js.map +1 -0
- package/dist/commands/ui/permissions.d.ts +14 -0
- package/dist/commands/ui/permissions.d.ts.map +1 -0
- package/dist/commands/ui/permissions.js +22 -0
- package/dist/commands/ui/permissions.js.map +1 -0
- package/dist/commands/ui/sessions.d.ts +13 -0
- package/dist/commands/ui/sessions.d.ts.map +1 -0
- package/dist/commands/ui/sessions.js +45 -0
- package/dist/commands/ui/sessions.js.map +1 -0
- package/dist/commands/ui/system-messages.d.ts +5 -0
- package/dist/commands/ui/system-messages.d.ts.map +1 -0
- package/dist/commands/ui/system-messages.js +19 -0
- package/dist/commands/ui/system-messages.js.map +1 -0
- package/dist/commands/ui/themes.d.ts +12 -0
- package/dist/commands/ui/themes.d.ts.map +1 -0
- package/dist/commands/ui/themes.js +40 -0
- package/dist/commands/ui/themes.js.map +1 -0
- package/dist/commands/ui/tmux.d.ts +5 -0
- package/dist/commands/ui/tmux.d.ts.map +1 -0
- package/dist/commands/ui/tmux.js +19 -0
- package/dist/commands/ui/tmux.js.map +1 -0
- package/dist/cortex.d.ts +12 -0
- package/dist/cortex.d.ts.map +1 -0
- package/dist/cortex.js +320 -0
- package/dist/cortex.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +112 -0
- package/dist/index.js.map +1 -0
- package/dist/ink-ui/CortexApp.d.ts +28 -0
- package/dist/ink-ui/CortexApp.d.ts.map +1 -0
- package/dist/ink-ui/CortexApp.js +2291 -0
- package/dist/ink-ui/CortexApp.js.map +1 -0
- package/dist/ink-ui/SimpleInputBuffer.d.ts +63 -0
- package/dist/ink-ui/SimpleInputBuffer.d.ts.map +1 -0
- package/dist/ink-ui/SimpleInputBuffer.js +369 -0
- package/dist/ink-ui/SimpleInputBuffer.js.map +1 -0
- package/dist/ink-ui/commands/slashCommands.d.ts +40 -0
- package/dist/ink-ui/commands/slashCommands.d.ts.map +1 -0
- package/dist/ink-ui/commands/slashCommands.js +111 -0
- package/dist/ink-ui/commands/slashCommands.js.map +1 -0
- package/dist/ink-ui/commands/types.d.ts +161 -0
- package/dist/ink-ui/commands/types.d.ts.map +1 -0
- package/dist/ink-ui/commands/types.js +12 -0
- package/dist/ink-ui/commands/types.js.map +1 -0
- package/dist/ink-ui/components/ApprovalDialog.d.ts +21 -0
- package/dist/ink-ui/components/ApprovalDialog.d.ts.map +1 -0
- package/dist/ink-ui/components/ApprovalDialog.js +81 -0
- package/dist/ink-ui/components/ApprovalDialog.js.map +1 -0
- package/dist/ink-ui/components/CommandSuggestions.d.ts +19 -0
- package/dist/ink-ui/components/CommandSuggestions.d.ts.map +1 -0
- package/dist/ink-ui/components/CommandSuggestions.js +36 -0
- package/dist/ink-ui/components/CommandSuggestions.js.map +1 -0
- package/dist/ink-ui/components/DiffPreview.d.ts +60 -0
- package/dist/ink-ui/components/DiffPreview.d.ts.map +1 -0
- package/dist/ink-ui/components/DiffPreview.js +195 -0
- package/dist/ink-ui/components/DiffPreview.js.map +1 -0
- package/dist/ink-ui/components/DocumentPreview.d.ts +50 -0
- package/dist/ink-ui/components/DocumentPreview.d.ts.map +1 -0
- package/dist/ink-ui/components/DocumentPreview.js +92 -0
- package/dist/ink-ui/components/DocumentPreview.js.map +1 -0
- package/dist/ink-ui/components/MarkdownText.d.ts +27 -0
- package/dist/ink-ui/components/MarkdownText.d.ts.map +1 -0
- package/dist/ink-ui/components/MarkdownText.js +342 -0
- package/dist/ink-ui/components/MarkdownText.js.map +1 -0
- package/dist/ink-ui/components/MenuRenderer.d.ts +26 -0
- package/dist/ink-ui/components/MenuRenderer.d.ts.map +1 -0
- package/dist/ink-ui/components/MenuRenderer.js +264 -0
- package/dist/ink-ui/components/MenuRenderer.js.map +1 -0
- package/dist/ink-ui/components/ModelPickerDialog.d.ts +33 -0
- package/dist/ink-ui/components/ModelPickerDialog.d.ts.map +1 -0
- package/dist/ink-ui/components/ModelPickerDialog.js +98 -0
- package/dist/ink-ui/components/ModelPickerDialog.js.map +1 -0
- package/dist/ink-ui/components/StreamDisplay.d.ts +31 -0
- package/dist/ink-ui/components/StreamDisplay.d.ts.map +1 -0
- package/dist/ink-ui/components/StreamDisplay.js +573 -0
- package/dist/ink-ui/components/StreamDisplay.js.map +1 -0
- package/dist/ink-ui/components/SubAgentPanel.d.ts +98 -0
- package/dist/ink-ui/components/SubAgentPanel.d.ts.map +1 -0
- package/dist/ink-ui/components/SubAgentPanel.js +155 -0
- package/dist/ink-ui/components/SubAgentPanel.js.map +1 -0
- package/dist/ink-ui/components/ThinkingDisplay.d.ts +46 -0
- package/dist/ink-ui/components/ThinkingDisplay.d.ts.map +1 -0
- package/dist/ink-ui/components/ThinkingDisplay.js +176 -0
- package/dist/ink-ui/components/ThinkingDisplay.js.map +1 -0
- package/dist/ink-ui/components/ToolCallDisplay.d.ts +51 -0
- package/dist/ink-ui/components/ToolCallDisplay.d.ts.map +1 -0
- package/dist/ink-ui/components/ToolCallDisplay.js +378 -0
- package/dist/ink-ui/components/ToolCallDisplay.js.map +1 -0
- package/dist/ink-ui/components/WritePreview.d.ts +42 -0
- package/dist/ink-ui/components/WritePreview.d.ts.map +1 -0
- package/dist/ink-ui/components/WritePreview.js +100 -0
- package/dist/ink-ui/components/WritePreview.js.map +1 -0
- package/dist/ink-ui/contexts/KeypressContext.d.ts +32 -0
- package/dist/ink-ui/contexts/KeypressContext.d.ts.map +1 -0
- package/dist/ink-ui/contexts/KeypressContext.js +529 -0
- package/dist/ink-ui/contexts/KeypressContext.js.map +1 -0
- package/dist/ink-ui/contexts/SessionContext.d.ts +46 -0
- package/dist/ink-ui/contexts/SessionContext.d.ts.map +1 -0
- package/dist/ink-ui/contexts/SessionContext.js +162 -0
- package/dist/ink-ui/contexts/SessionContext.js.map +1 -0
- package/dist/ink-ui/core-stubs.d.ts +270 -0
- package/dist/ink-ui/core-stubs.d.ts.map +1 -0
- package/dist/ink-ui/core-stubs.js +276 -0
- package/dist/ink-ui/core-stubs.js.map +1 -0
- package/dist/ink-ui/cortex-types.d.ts +214 -0
- package/dist/ink-ui/cortex-types.d.ts.map +1 -0
- package/dist/ink-ui/cortex-types.js +52 -0
- package/dist/ink-ui/cortex-types.js.map +1 -0
- package/dist/ink-ui/hooks/useCortexStream.d.ts +75 -0
- package/dist/ink-ui/hooks/useCortexStream.d.ts.map +1 -0
- package/dist/ink-ui/hooks/useCortexStream.js +518 -0
- package/dist/ink-ui/hooks/useCortexStream.js.map +1 -0
- package/dist/ink-ui/hooks/useFocus.d.ts +11 -0
- package/dist/ink-ui/hooks/useFocus.d.ts.map +1 -0
- package/dist/ink-ui/hooks/useFocus.js +51 -0
- package/dist/ink-ui/hooks/useFocus.js.map +1 -0
- package/dist/ink-ui/hooks/useHistoryManager.d.ts +23 -0
- package/dist/ink-ui/hooks/useHistoryManager.d.ts.map +1 -0
- package/dist/ink-ui/hooks/useHistoryManager.js +63 -0
- package/dist/ink-ui/hooks/useHistoryManager.js.map +1 -0
- package/dist/ink-ui/hooks/useKeypress.d.ts +18 -0
- package/dist/ink-ui/hooks/useKeypress.d.ts.map +1 -0
- package/dist/ink-ui/hooks/useKeypress.js +27 -0
- package/dist/ink-ui/hooks/useKeypress.js.map +1 -0
- package/dist/ink-ui/hooks/useReactApprovalHandler.d.ts +74 -0
- package/dist/ink-ui/hooks/useReactApprovalHandler.d.ts.map +1 -0
- package/dist/ink-ui/hooks/useReactApprovalHandler.js +83 -0
- package/dist/ink-ui/hooks/useReactApprovalHandler.js.map +1 -0
- package/dist/ink-ui/hooks/useTerminalSize.d.ts +10 -0
- package/dist/ink-ui/hooks/useTerminalSize.d.ts.map +1 -0
- package/dist/ink-ui/hooks/useTerminalSize.js +28 -0
- package/dist/ink-ui/hooks/useTerminalSize.js.map +1 -0
- package/dist/ink-ui/semantic-colors.d.ts +8 -0
- package/dist/ink-ui/semantic-colors.d.ts.map +1 -0
- package/dist/ink-ui/semantic-colors.js +24 -0
- package/dist/ink-ui/semantic-colors.js.map +1 -0
- package/dist/ink-ui/state/extensions.d.ts +68 -0
- package/dist/ink-ui/state/extensions.d.ts.map +1 -0
- package/dist/ink-ui/state/extensions.js +97 -0
- package/dist/ink-ui/state/extensions.js.map +1 -0
- package/dist/ink-ui/themes/ansi-light.d.ts +8 -0
- package/dist/ink-ui/themes/ansi-light.d.ts.map +1 -0
- package/dist/ink-ui/themes/ansi-light.js +143 -0
- package/dist/ink-ui/themes/ansi-light.js.map +1 -0
- package/dist/ink-ui/themes/ansi.d.ts +8 -0
- package/dist/ink-ui/themes/ansi.d.ts.map +1 -0
- package/dist/ink-ui/themes/ansi.js +153 -0
- package/dist/ink-ui/themes/ansi.js.map +1 -0
- package/dist/ink-ui/themes/atom-one-dark.d.ts +8 -0
- package/dist/ink-ui/themes/atom-one-dark.d.ts.map +1 -0
- package/dist/ink-ui/themes/atom-one-dark.js +140 -0
- package/dist/ink-ui/themes/atom-one-dark.js.map +1 -0
- package/dist/ink-ui/themes/ayu-light.d.ts +8 -0
- package/dist/ink-ui/themes/ayu-light.d.ts.map +1 -0
- package/dist/ink-ui/themes/ayu-light.js +132 -0
- package/dist/ink-ui/themes/ayu-light.js.map +1 -0
- package/dist/ink-ui/themes/ayu.d.ts +8 -0
- package/dist/ink-ui/themes/ayu.d.ts.map +1 -0
- package/dist/ink-ui/themes/ayu.js +106 -0
- package/dist/ink-ui/themes/ayu.js.map +1 -0
- package/dist/ink-ui/themes/color-utils.d.ts +23 -0
- package/dist/ink-ui/themes/color-utils.d.ts.map +1 -0
- package/dist/ink-ui/themes/color-utils.js +237 -0
- package/dist/ink-ui/themes/color-utils.js.map +1 -0
- package/dist/ink-ui/themes/default-light.d.ts +8 -0
- package/dist/ink-ui/themes/default-light.d.ts.map +1 -0
- package/dist/ink-ui/themes/default-light.js +100 -0
- package/dist/ink-ui/themes/default-light.js.map +1 -0
- package/dist/ink-ui/themes/default.d.ts +8 -0
- package/dist/ink-ui/themes/default.d.ts.map +1 -0
- package/dist/ink-ui/themes/default.js +143 -0
- package/dist/ink-ui/themes/default.js.map +1 -0
- package/dist/ink-ui/themes/dracula.d.ts +8 -0
- package/dist/ink-ui/themes/dracula.d.ts.map +1 -0
- package/dist/ink-ui/themes/dracula.js +117 -0
- package/dist/ink-ui/themes/dracula.js.map +1 -0
- package/dist/ink-ui/themes/github-dark.d.ts +8 -0
- package/dist/ink-ui/themes/github-dark.d.ts.map +1 -0
- package/dist/ink-ui/themes/github-dark.js +140 -0
- package/dist/ink-ui/themes/github-dark.js.map +1 -0
- package/dist/ink-ui/themes/github-light.d.ts +8 -0
- package/dist/ink-ui/themes/github-light.d.ts.map +1 -0
- package/dist/ink-ui/themes/github-light.js +142 -0
- package/dist/ink-ui/themes/github-light.js.map +1 -0
- package/dist/ink-ui/themes/googlecode.d.ts +8 -0
- package/dist/ink-ui/themes/googlecode.d.ts.map +1 -0
- package/dist/ink-ui/themes/googlecode.js +139 -0
- package/dist/ink-ui/themes/googlecode.js.map +1 -0
- package/dist/ink-ui/themes/no-color.d.ts +8 -0
- package/dist/ink-ui/themes/no-color.d.ts.map +1 -0
- package/dist/ink-ui/themes/no-color.js +118 -0
- package/dist/ink-ui/themes/no-color.js.map +1 -0
- package/dist/ink-ui/themes/semantic-tokens.d.ts +40 -0
- package/dist/ink-ui/themes/semantic-tokens.d.ts.map +1 -0
- package/dist/ink-ui/themes/semantic-tokens.js +100 -0
- package/dist/ink-ui/themes/semantic-tokens.js.map +1 -0
- package/dist/ink-ui/themes/shades-of-purple.d.ts +12 -0
- package/dist/ink-ui/themes/shades-of-purple.d.ts.map +1 -0
- package/dist/ink-ui/themes/shades-of-purple.js +306 -0
- package/dist/ink-ui/themes/shades-of-purple.js.map +1 -0
- package/dist/ink-ui/themes/theme-manager.d.ts +67 -0
- package/dist/ink-ui/themes/theme-manager.d.ts.map +1 -0
- package/dist/ink-ui/themes/theme-manager.js +263 -0
- package/dist/ink-ui/themes/theme-manager.js.map +1 -0
- package/dist/ink-ui/themes/theme.d.ts +134 -0
- package/dist/ink-ui/themes/theme.d.ts.map +1 -0
- package/dist/ink-ui/themes/theme.js +390 -0
- package/dist/ink-ui/themes/theme.js.map +1 -0
- package/dist/ink-ui/themes/xcode.d.ts +8 -0
- package/dist/ink-ui/themes/xcode.d.ts.map +1 -0
- package/dist/ink-ui/themes/xcode.js +147 -0
- package/dist/ink-ui/themes/xcode.js.map +1 -0
- package/dist/ink-ui/types.d.ts +306 -0
- package/dist/ink-ui/types.d.ts.map +1 -0
- package/dist/ink-ui/types.js +65 -0
- package/dist/ink-ui/types.js.map +1 -0
- package/dist/ink-ui/utils/input.d.ts +18 -0
- package/dist/ink-ui/utils/input.d.ts.map +1 -0
- package/dist/ink-ui/utils/input.js +51 -0
- package/dist/ink-ui/utils/input.js.map +1 -0
- package/dist/ink-ui/utils/mouse.d.ts +33 -0
- package/dist/ink-ui/utils/mouse.d.ts.map +1 -0
- package/dist/ink-ui/utils/mouse.js +182 -0
- package/dist/ink-ui/utils/mouse.js.map +1 -0
- package/dist/services/BuiltinCommandLoader.d.ts +21 -0
- package/dist/services/BuiltinCommandLoader.d.ts.map +1 -0
- package/dist/services/BuiltinCommandLoader.js +182 -0
- package/dist/services/BuiltinCommandLoader.js.map +1 -0
- package/dist/services/CommandService.d.ts +39 -0
- package/dist/services/CommandService.d.ts.map +1 -0
- package/dist/services/CommandService.js +49 -0
- package/dist/services/CommandService.js.map +1 -0
- package/dist/services/FileCommandLoader.d.ts +16 -0
- package/dist/services/FileCommandLoader.d.ts.map +1 -0
- package/dist/services/FileCommandLoader.js +19 -0
- package/dist/services/FileCommandLoader.js.map +1 -0
- package/dist/services/McpPromptLoader.d.ts +16 -0
- package/dist/services/McpPromptLoader.d.ts.map +1 -0
- package/dist/services/McpPromptLoader.js +19 -0
- package/dist/services/McpPromptLoader.js.map +1 -0
- package/dist/services/index.d.ts +8 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +8 -0
- package/dist/services/index.js.map +1 -0
- package/dist/ui/ChalkMenuRenderer.d.ts +22 -0
- package/dist/ui/ChalkMenuRenderer.d.ts.map +1 -0
- package/dist/ui/ChalkMenuRenderer.js +478 -0
- package/dist/ui/ChalkMenuRenderer.js.map +1 -0
- package/dist/ui/ChalkModelPicker.d.ts +20 -0
- package/dist/ui/ChalkModelPicker.d.ts.map +1 -0
- package/dist/ui/ChalkModelPicker.js +363 -0
- package/dist/ui/ChalkModelPicker.js.map +1 -0
- package/dist/ui/ChalkThemePicker.d.ts +20 -0
- package/dist/ui/ChalkThemePicker.d.ts.map +1 -0
- package/dist/ui/ChalkThemePicker.js +370 -0
- package/dist/ui/ChalkThemePicker.js.map +1 -0
- package/dist/ui/InkApprovalDialog.d.ts +8 -0
- package/dist/ui/InkApprovalDialog.d.ts.map +1 -0
- package/dist/ui/InkApprovalDialog.js +25 -0
- package/dist/ui/InkApprovalDialog.js.map +1 -0
- package/dist/ui/InkCommandPalette.d.ts +43 -0
- package/dist/ui/InkCommandPalette.d.ts.map +1 -0
- package/dist/ui/InkCommandPalette.js +105 -0
- package/dist/ui/InkCommandPalette.js.map +1 -0
- package/dist/ui/InkConfigMenu.d.ts +24 -0
- package/dist/ui/InkConfigMenu.d.ts.map +1 -0
- package/dist/ui/InkConfigMenu.js +259 -0
- package/dist/ui/InkConfigMenu.js.map +1 -0
- package/dist/ui/InkHelp.d.ts +25 -0
- package/dist/ui/InkHelp.d.ts.map +1 -0
- package/dist/ui/InkHelp.js +55 -0
- package/dist/ui/InkHelp.js.map +1 -0
- package/dist/ui/InkMenuPicker.d.ts +19 -0
- package/dist/ui/InkMenuPicker.d.ts.map +1 -0
- package/dist/ui/InkMenuPicker.js +33 -0
- package/dist/ui/InkMenuPicker.js.map +1 -0
- package/dist/ui/InkModelPicker.d.ts +23 -0
- package/dist/ui/InkModelPicker.d.ts.map +1 -0
- package/dist/ui/InkModelPicker.js +53 -0
- package/dist/ui/InkModelPicker.js.map +1 -0
- package/dist/ui/PersistentInput.d.ts +91 -0
- package/dist/ui/PersistentInput.d.ts.map +1 -0
- package/dist/ui/PersistentInput.js +283 -0
- package/dist/ui/PersistentInput.js.map +1 -0
- package/dist/ui/RawInput.d.ts +27 -0
- package/dist/ui/RawInput.d.ts.map +1 -0
- package/dist/ui/RawInput.js +731 -0
- package/dist/ui/RawInput.js.map +1 -0
- package/dist/ui/SlashCommandAutocomplete.d.ts +94 -0
- package/dist/ui/SlashCommandAutocomplete.d.ts.map +1 -0
- package/dist/ui/SlashCommandAutocomplete.js +274 -0
- package/dist/ui/SlashCommandAutocomplete.js.map +1 -0
- package/dist/ui/SplashScreen.d.ts +76 -0
- package/dist/ui/SplashScreen.d.ts.map +1 -0
- package/dist/ui/SplashScreen.js +316 -0
- package/dist/ui/SplashScreen.js.map +1 -0
- package/dist/ui/components/ArtifactDashboard.d.ts +16 -0
- package/dist/ui/components/ArtifactDashboard.d.ts.map +1 -0
- package/dist/ui/components/ArtifactDashboard.js +102 -0
- package/dist/ui/components/ArtifactDashboard.js.map +1 -0
- package/dist/ui/components/ChatInput.d.ts +36 -0
- package/dist/ui/components/ChatInput.d.ts.map +1 -0
- package/dist/ui/components/ChatInput.js +11 -0
- package/dist/ui/components/ChatInput.js.map +1 -0
- package/dist/ui/components/ConfigWizard.d.ts +17 -0
- package/dist/ui/components/ConfigWizard.d.ts.map +1 -0
- package/dist/ui/components/ConfigWizard.js +134 -0
- package/dist/ui/components/ConfigWizard.js.map +1 -0
- package/dist/ui/components/ContextViewer.d.ts +18 -0
- package/dist/ui/components/ContextViewer.d.ts.map +1 -0
- package/dist/ui/components/ContextViewer.js +85 -0
- package/dist/ui/components/ContextViewer.js.map +1 -0
- package/dist/ui/components/MiddlewareDashboard.d.ts +16 -0
- package/dist/ui/components/MiddlewareDashboard.d.ts.map +1 -0
- package/dist/ui/components/MiddlewareDashboard.js +98 -0
- package/dist/ui/components/MiddlewareDashboard.js.map +1 -0
- package/dist/ui/components/ModelPicker.d.ts +24 -0
- package/dist/ui/components/ModelPicker.d.ts.map +1 -0
- package/dist/ui/components/ModelPicker.js +96 -0
- package/dist/ui/components/ModelPicker.js.map +1 -0
- package/dist/ui/components/MultiLineInput.d.ts +27 -0
- package/dist/ui/components/MultiLineInput.d.ts.map +1 -0
- package/dist/ui/components/MultiLineInput.js +182 -0
- package/dist/ui/components/MultiLineInput.js.map +1 -0
- package/dist/ui/components/PermissionsBrowser.d.ts +16 -0
- package/dist/ui/components/PermissionsBrowser.d.ts.map +1 -0
- package/dist/ui/components/PermissionsBrowser.js +110 -0
- package/dist/ui/components/PermissionsBrowser.js.map +1 -0
- package/dist/ui/components/SessionBrowser.d.ts +19 -0
- package/dist/ui/components/SessionBrowser.d.ts.map +1 -0
- package/dist/ui/components/SessionBrowser.js +66 -0
- package/dist/ui/components/SessionBrowser.js.map +1 -0
- package/dist/ui/components/StatusBar.d.ts +32 -0
- package/dist/ui/components/StatusBar.d.ts.map +1 -0
- package/dist/ui/components/StatusBar.js +27 -0
- package/dist/ui/components/StatusBar.js.map +1 -0
- package/dist/ui/components/SystemMessageBrowser.d.ts +11 -0
- package/dist/ui/components/SystemMessageBrowser.d.ts.map +1 -0
- package/dist/ui/components/SystemMessageBrowser.js +106 -0
- package/dist/ui/components/SystemMessageBrowser.js.map +1 -0
- package/dist/ui/components/ThemePicker.d.ts +11 -0
- package/dist/ui/components/ThemePicker.d.ts.map +1 -0
- package/dist/ui/components/ThemePicker.js +62 -0
- package/dist/ui/components/ThemePicker.js.map +1 -0
- package/dist/ui/components/TmuxBrowser.d.ts +11 -0
- package/dist/ui/components/TmuxBrowser.d.ts.map +1 -0
- package/dist/ui/components/TmuxBrowser.js +85 -0
- package/dist/ui/components/TmuxBrowser.js.map +1 -0
- package/dist/ui/components/index.d.ts +22 -0
- package/dist/ui/components/index.d.ts.map +1 -0
- package/dist/ui/components/index.js +21 -0
- package/dist/ui/components/index.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,2291 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Nexus Cortex - Main Application Component
|
|
4
|
+
*
|
|
5
|
+
* This is the entry point for the Ink-based UI, adapted from Gemini CLI
|
|
6
|
+
* but wired to our @nexus-cortex/core orchestrator.
|
|
7
|
+
*
|
|
8
|
+
* Architecture:
|
|
9
|
+
* - CortexApp (this file) - Application shell and state management
|
|
10
|
+
* - useCortexStream - Bridge hook to orchestrator
|
|
11
|
+
* - Gemini UI components - Rendering (DiffRenderer, InputPrompt, etc.)
|
|
12
|
+
*/
|
|
13
|
+
import React, { useState, useCallback, useEffect, useMemo, useRef, } from 'react';
|
|
14
|
+
import fs, { realpathSync } from 'fs';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import { fileURLToPath } from 'url';
|
|
17
|
+
import { Box, Text, Static, useApp, useInput, useStdout, useStdin, } from 'ink';
|
|
18
|
+
import { createInputBuffer } from './SimpleInputBuffer.js';
|
|
19
|
+
import { OrchestratorClient } from '@nexus-cortex/cli/dist/orchestrator/OrchestratorClient.js';
|
|
20
|
+
import { useCortexStream } from './hooks/useCortexStream.js';
|
|
21
|
+
import { useTerminalSize } from './hooks/useTerminalSize.js';
|
|
22
|
+
// useHistory hook removed - using orchestrator history directly
|
|
23
|
+
// import { useHistory } from './hooks/useHistoryManager.js';
|
|
24
|
+
import { useReactApprovalHandler } from './hooks/useReactApprovalHandler.js';
|
|
25
|
+
import { StreamingState, MessageType } from './cortex-types.js';
|
|
26
|
+
import { Colors, setTheme, getThemeNames, getThemeDefinition, initializeTheme, persistModel, loadPersistedModel } from '@nexus-cortex/cli/dist/themes/colors.js';
|
|
27
|
+
import { ApprovalDialog } from './components/ApprovalDialog.js';
|
|
28
|
+
import { InlineThinkingDisplay } from './components/ThinkingDisplay.js';
|
|
29
|
+
import { StreamDisplay } from './components/StreamDisplay.js';
|
|
30
|
+
import { UnifiedDiffDisplay } from './components/DiffPreview.js';
|
|
31
|
+
import { WritePreview } from './components/WritePreview.js';
|
|
32
|
+
import { DocumentPreview } from './components/DocumentPreview.js';
|
|
33
|
+
import { MarkdownText } from './components/MarkdownText.js';
|
|
34
|
+
import { CommandSuggestions } from './components/CommandSuggestions.js';
|
|
35
|
+
import { getSuggestions } from './commands/slashCommands.js';
|
|
36
|
+
import { InteractiveMenu } from '../commands/system-message/InteractiveMenu.js';
|
|
37
|
+
import { SystemMessageStore } from '@nexus-cortex/core/system-messages/SystemMessageStore.js';
|
|
38
|
+
import { MentorshipConfigService, slashCommandRegistry } from '@nexus-cortex/core';
|
|
39
|
+
import { ModelPickerDialog } from './components/ModelPickerDialog.js';
|
|
40
|
+
import { MenuRenderer } from './components/MenuRenderer.js';
|
|
41
|
+
import { SubAgentPanel, createSubAgentStateManager } from './components/SubAgentPanel.js';
|
|
42
|
+
import { ConfigMenu } from '../ui/InkConfigMenu.js';
|
|
43
|
+
// Get installation root from this file's location
|
|
44
|
+
// Use realpathSync to resolve symlinks (important for npm link)
|
|
45
|
+
// Compiled file is at: packages/cli/dist/ink-ui/CortexApp.js
|
|
46
|
+
// Installation root is 4 levels up: ink-ui -> dist -> cli -> packages -> root
|
|
47
|
+
const __filename = realpathSync(fileURLToPath(import.meta.url));
|
|
48
|
+
const __dirname = path.dirname(__filename);
|
|
49
|
+
const CLI_INSTALLATION_ROOT = path.join(__dirname, '..', '..', '..', '..');
|
|
50
|
+
/**
|
|
51
|
+
* Compact header - CC-style 3-line display: name + model + cwd
|
|
52
|
+
*/
|
|
53
|
+
const CompactHeader = ({ model, cwd }) => {
|
|
54
|
+
const home = process.env.HOME || '';
|
|
55
|
+
const displayCwd = home && cwd.startsWith(home) ? '~' + cwd.slice(home.length) : cwd;
|
|
56
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: Colors.primary, bold: true, children: "CORTEX" }), _jsx(Text, { dimColor: true, children: " \u00B7 " }), _jsx(Text, { color: Colors.AccentGreen, children: model }), _jsx(Text, { dimColor: true, children: " \u00B7 " }), _jsx(Text, { color: Colors.AccentBlue, children: displayCwd })] }), _jsx(Text, { dimColor: true, children: "Tab: thinking | Shift+Tab: auto-approve | /help: commands | ESC: abort" })] }));
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Status Line component - displays at bottom below input
|
|
60
|
+
* Shows model, status indicators, and keyboard shortcuts
|
|
61
|
+
*/
|
|
62
|
+
const StatusLine = ({ model, autoApprove, showThinking, reasoningEffort, supportsReasoning }) => {
|
|
63
|
+
return (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: Colors.AccentGreen, children: model }), showThinking && _jsx(Text, { color: Colors.AccentBlue, children: " [Think]" }), supportsReasoning && reasoningEffort && reasoningEffort !== 'none' && (_jsxs(Text, { color: Colors.AccentPurple, children: [" [R:", reasoningEffort, "]"] })), autoApprove && _jsx(Text, { color: Colors.AccentYellow, children: " [Auto-Approve]" })] }));
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Format tool input for display (compact summary)
|
|
67
|
+
*/
|
|
68
|
+
function formatToolInputForDisplay(name, input) {
|
|
69
|
+
if (!input)
|
|
70
|
+
return '';
|
|
71
|
+
switch (name) {
|
|
72
|
+
case 'Read':
|
|
73
|
+
case 'Write':
|
|
74
|
+
case 'Edit':
|
|
75
|
+
return input.file_path || '';
|
|
76
|
+
case 'Glob':
|
|
77
|
+
case 'Grep':
|
|
78
|
+
return input.pattern || '';
|
|
79
|
+
case 'Bash':
|
|
80
|
+
const cmd = input.command || '';
|
|
81
|
+
return cmd.length > 50 ? cmd.slice(0, 50) + '...' : cmd;
|
|
82
|
+
default:
|
|
83
|
+
const first = Object.values(input).find(v => typeof v === 'string');
|
|
84
|
+
if (first) {
|
|
85
|
+
const s = String(first);
|
|
86
|
+
return s.length > 50 ? s.slice(0, 50) + '...' : s;
|
|
87
|
+
}
|
|
88
|
+
return '';
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Message display component - memoized to prevent re-renders on input changes
|
|
93
|
+
*/
|
|
94
|
+
const MessageDisplay = React.memo(({ item, terminalWidth, showThinking = false, expandedDocuments = new Set(), documentContents = new Map() }) => {
|
|
95
|
+
// Calculate content width accounting for prefix characters
|
|
96
|
+
const contentWidth = Math.max(terminalWidth - 2, 40); // "◆ " or "❯ " = 2 chars
|
|
97
|
+
switch (item.type) {
|
|
98
|
+
case MessageType.User:
|
|
99
|
+
return (_jsxs(Box, { marginY: 1, children: [_jsx(Text, { color: Colors.AccentBlue, children: "\u276F " }), _jsx(Text, { children: typeof item.userContent === 'string' ? item.userContent : item.userContent?.text })] }));
|
|
100
|
+
case MessageType.Model:
|
|
101
|
+
const modelItem = item;
|
|
102
|
+
return (_jsxs(Box, { marginY: 1, flexDirection: "column", children: [showThinking && modelItem.thought && (_jsx(InlineThinkingDisplay, { text: modelItem.thought, isComplete: true, width: terminalWidth })), _jsxs(Box, { children: [_jsx(Text, { color: Colors.AccentGreen, children: "\u25C6 " }), _jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, children: _jsx(MarkdownText, { width: contentWidth, children: modelItem.modelContent || '' }) })] })] }));
|
|
103
|
+
case MessageType.Error:
|
|
104
|
+
return (_jsxs(Box, { marginY: 1, children: [_jsx(Text, { color: Colors.AccentRed, children: "\u2717 Error: " }), _jsx(Text, { color: Colors.AccentRed, children: item.errorContent })] }));
|
|
105
|
+
case MessageType.Info:
|
|
106
|
+
return (_jsx(Box, { marginY: 1, children: _jsxs(Text, { dimColor: true, children: ["\u2139 ", item.infoContent] }) }));
|
|
107
|
+
case 'orchestrator': {
|
|
108
|
+
// Render orchestrator message from core library
|
|
109
|
+
const orchestratorItem = item;
|
|
110
|
+
const msg = orchestratorItem.message;
|
|
111
|
+
if (!msg)
|
|
112
|
+
return null;
|
|
113
|
+
const role = msg.message?.role || msg.type;
|
|
114
|
+
const content = msg.message?.content;
|
|
115
|
+
// User message - check for tool_result with Edit diff or Write preview first
|
|
116
|
+
if (role === 'user') {
|
|
117
|
+
// Check if this is a tool_result message with Edit diffs or Write previews
|
|
118
|
+
if (Array.isArray(content)) {
|
|
119
|
+
const editResults = content.filter((b) => b.type === 'tool_result' &&
|
|
120
|
+
b.tool_name?.toLowerCase() === 'edit' &&
|
|
121
|
+
b.metadata?.diff &&
|
|
122
|
+
!b.is_error);
|
|
123
|
+
const writeResults = content.filter((b) => b.type === 'tool_result' &&
|
|
124
|
+
b.tool_name?.toLowerCase() === 'write' &&
|
|
125
|
+
b.metadata?.writePreview &&
|
|
126
|
+
!b.is_error);
|
|
127
|
+
const documentResults = content.filter((b) => b.type === 'tool_result' &&
|
|
128
|
+
b.tool_name?.toLowerCase() === 'write' &&
|
|
129
|
+
b.metadata?.documentPreview &&
|
|
130
|
+
!b.is_error);
|
|
131
|
+
if (editResults.length > 0 || writeResults.length > 0 || documentResults.length > 0) {
|
|
132
|
+
// Render Edit tool results with diffs, Write tool results with previews, and Document previews
|
|
133
|
+
return (_jsxs(Box, { marginY: 1, flexDirection: "column", children: [editResults.map((block, i) => (_jsx(Box, { marginLeft: 2, children: _jsx(UnifiedDiffDisplay, { diffString: block.metadata.diff, filePath: block.metadata?.fileStats?.path || '', compact: false, fileStats: block.metadata?.fileStats }) }, `${msg.uuid}-edit-result-${i}`))), writeResults.map((block, i) => (_jsx(Box, { marginLeft: 2, children: _jsx(WritePreview, { filePath: block.metadata.writePreview.filePath, content: block.metadata.writePreview.content, lineCount: block.metadata.writePreview.lineCount, byteSize: block.metadata.writePreview.byteSize, language: block.metadata.writePreview.language, terminalWidth: terminalWidth - 4 }) }, `${msg.uuid}-write-result-${i}`))), documentResults.map((block, i) => {
|
|
134
|
+
const docId = `${msg.uuid}-doc-${i}`;
|
|
135
|
+
const isExpanded = expandedDocuments.has(docId);
|
|
136
|
+
const docContent = documentContents.get(docId);
|
|
137
|
+
return (_jsx(Box, { marginLeft: 2, children: _jsx(DocumentPreview, { docId: docId, filePath: block.metadata.documentPreview.filePath, lineCount: block.metadata.documentPreview.lineCount, wordCount: block.metadata.documentPreview.wordCount, isMarkdown: block.metadata.documentPreview.isMarkdown, isExpanded: isExpanded, content: docContent ?? undefined, isLoading: isExpanded && !documentContents.has(docId), terminalWidth: terminalWidth - 2 }) }, docId));
|
|
138
|
+
})] }));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Regular user text message
|
|
142
|
+
const text = typeof content === 'string'
|
|
143
|
+
? content
|
|
144
|
+
: Array.isArray(content)
|
|
145
|
+
? content.find((b) => b.type === 'text')?.text || ''
|
|
146
|
+
: '';
|
|
147
|
+
if (!text)
|
|
148
|
+
return null; // Skip empty tool_result messages
|
|
149
|
+
return (_jsxs(Box, { marginY: 1, children: [_jsx(Text, { color: Colors.AccentBlue, children: "\u276F " }), _jsx(Text, { children: text })] }));
|
|
150
|
+
}
|
|
151
|
+
// Assistant message - render content blocks in order
|
|
152
|
+
if (role === 'assistant' && Array.isArray(content)) {
|
|
153
|
+
return (_jsx(Box, { marginY: 1, flexDirection: "column", children: content.map((block, i) => {
|
|
154
|
+
if (block.type === 'text' && block.text) {
|
|
155
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: Colors.AccentGreen, children: "\u25C6 " }), _jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, children: _jsx(MarkdownText, { width: contentWidth, children: block.text }) })] }, `${msg.uuid}-text-${i}`));
|
|
156
|
+
}
|
|
157
|
+
// Interleaved thinking - ALWAYS show (it's natural model output)
|
|
158
|
+
// This is NOT controlled by Tab toggle - that's for extended thinking only
|
|
159
|
+
if (block.type === 'thinking' && block.thinking) {
|
|
160
|
+
// Determine thinking type for visual distinction
|
|
161
|
+
const thinkingBlock = block;
|
|
162
|
+
let thinkingType = 'native';
|
|
163
|
+
// Check metadata first (explicitly set during conversion)
|
|
164
|
+
if (thinkingBlock.thinkingMetadata?.source) {
|
|
165
|
+
thinkingType = thinkingBlock.thinkingMetadata.source;
|
|
166
|
+
}
|
|
167
|
+
else if (thinkingBlock.signature) {
|
|
168
|
+
// Has signature = extended thinking (Opus + Tab)
|
|
169
|
+
thinkingType = 'extended';
|
|
170
|
+
}
|
|
171
|
+
return (_jsx(InlineThinkingDisplay, { text: block.thinking, isComplete: true, width: terminalWidth, thinkingType: thinkingType }, `${msg.uuid}-thinking-${i}`));
|
|
172
|
+
}
|
|
173
|
+
if (block.type === 'tool_use' && block.toolUse) {
|
|
174
|
+
const toolName = block.toolUse.name;
|
|
175
|
+
const input = block.toolUse.input || {};
|
|
176
|
+
const inputStr = formatToolInputForDisplay(toolName, input);
|
|
177
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: Colors.AccentCyan, children: "\u25CF " }), _jsx(Text, { bold: true, children: toolName }), inputStr && _jsxs(Text, { dimColor: true, children: [" (", inputStr, ")"] })] }, `${msg.uuid}-tool-${i}`));
|
|
178
|
+
}
|
|
179
|
+
if (block.type === 'tool_result') {
|
|
180
|
+
const isError = block.is_error;
|
|
181
|
+
const isEdit = block.tool_name?.toLowerCase() === 'edit';
|
|
182
|
+
const isWrite = block.tool_name?.toLowerCase() === 'write';
|
|
183
|
+
const hasDiff = block.metadata?.diff;
|
|
184
|
+
const hasWritePreview = block.metadata?.writePreview;
|
|
185
|
+
const filePath = block.metadata?.fileStats?.path || '';
|
|
186
|
+
// For Edit tools with diff, show the diff inline
|
|
187
|
+
if (isEdit && hasDiff && !isError) {
|
|
188
|
+
return (_jsx(Box, { flexDirection: "column", marginLeft: 2, children: _jsx(UnifiedDiffDisplay, { diffString: block.metadata.diff, filePath: filePath, compact: false, fileStats: block.metadata.fileStats }) }, `${msg.uuid}-result-${i}`));
|
|
189
|
+
}
|
|
190
|
+
// For Write tools with preview, show the file content
|
|
191
|
+
if (isWrite && hasWritePreview && !isError) {
|
|
192
|
+
return (_jsx(Box, { flexDirection: "column", marginLeft: 2, children: _jsx(WritePreview, { filePath: block.metadata.writePreview.filePath, content: block.metadata.writePreview.content, lineCount: block.metadata.writePreview.lineCount, byteSize: block.metadata.writePreview.byteSize, language: block.metadata.writePreview.language, terminalWidth: terminalWidth - 4 }) }, `${msg.uuid}-result-${i}`));
|
|
193
|
+
}
|
|
194
|
+
// For Write tools with document preview (markdown/txt), show collapsible preview
|
|
195
|
+
const hasDocumentPreview = block.metadata?.documentPreview;
|
|
196
|
+
if (isWrite && hasDocumentPreview && !isError) {
|
|
197
|
+
const docId = `${msg.uuid}-doc-${i}`;
|
|
198
|
+
const isExpanded = expandedDocuments.has(docId);
|
|
199
|
+
const docContent = documentContents.get(docId);
|
|
200
|
+
return (_jsx(Box, { flexDirection: "column", marginLeft: 2, children: _jsx(DocumentPreview, { docId: docId, filePath: block.metadata.documentPreview.filePath, lineCount: block.metadata.documentPreview.lineCount, wordCount: block.metadata.documentPreview.wordCount, isMarkdown: block.metadata.documentPreview.isMarkdown, isExpanded: isExpanded, content: docContent ?? undefined, isLoading: isExpanded && !documentContents.has(docId), terminalWidth: terminalWidth - 2 }) }, `${msg.uuid}-result-${i}`));
|
|
201
|
+
}
|
|
202
|
+
// For other tools or Edit without diff, show simple status
|
|
203
|
+
return (_jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { dimColor: true, children: "\u23BF " }), _jsx(Text, { color: isError ? Colors.AccentRed : Colors.AccentGreen, children: isError ? 'Error' : 'Done' })] }, `${msg.uuid}-result-${i}`));
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}) }));
|
|
207
|
+
}
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
case MessageType.ToolGroup: {
|
|
211
|
+
// Render tool group with diffs for Edit tools and previews for Write tools
|
|
212
|
+
const tools = item.tools || [];
|
|
213
|
+
return (_jsx(Box, { marginY: 1, flexDirection: "column", children: tools.map((tool, i) => {
|
|
214
|
+
const isEdit = tool.name?.toLowerCase() === 'edit';
|
|
215
|
+
const isWrite = tool.name?.toLowerCase() === 'write';
|
|
216
|
+
const hasDiff = tool.resultDisplay?.metadata?.diff;
|
|
217
|
+
const hasWritePreview = tool.resultDisplay?.metadata?.writePreview;
|
|
218
|
+
const isSuccess = tool.status === 'Success';
|
|
219
|
+
const filePath = tool.resultDisplay?.metadata?.fileStats?.path || '';
|
|
220
|
+
if (isEdit && isSuccess && hasDiff) {
|
|
221
|
+
// Show full diff for Edit tools
|
|
222
|
+
return (_jsx(Box, { flexDirection: "column", children: _jsx(UnifiedDiffDisplay, { diffString: tool.resultDisplay.metadata.diff, filePath: filePath, compact: false, fileStats: tool.resultDisplay.metadata.fileStats }) }, `tool-${i}`));
|
|
223
|
+
}
|
|
224
|
+
if (isWrite && isSuccess && hasWritePreview) {
|
|
225
|
+
// Show file content preview for Write tools
|
|
226
|
+
return (_jsx(Box, { flexDirection: "column", children: _jsx(WritePreview, { filePath: tool.resultDisplay.metadata.writePreview.filePath, content: tool.resultDisplay.metadata.writePreview.content, lineCount: tool.resultDisplay.metadata.writePreview.lineCount, byteSize: tool.resultDisplay.metadata.writePreview.byteSize, language: tool.resultDisplay.metadata.writePreview.language, terminalWidth: terminalWidth }) }, `tool-${i}`));
|
|
227
|
+
}
|
|
228
|
+
// Show document preview for markdown/text files
|
|
229
|
+
const hasDocumentPreview = tool.resultDisplay?.metadata?.documentPreview;
|
|
230
|
+
if (isWrite && isSuccess && hasDocumentPreview) {
|
|
231
|
+
const docId = `${item.id}-tool-doc-${i}`;
|
|
232
|
+
const isExpanded = expandedDocuments.has(docId);
|
|
233
|
+
const docContent = documentContents.get(docId);
|
|
234
|
+
return (_jsx(Box, { flexDirection: "column", children: _jsx(DocumentPreview, { docId: docId, filePath: tool.resultDisplay.metadata.documentPreview.filePath, lineCount: tool.resultDisplay.metadata.documentPreview.lineCount, wordCount: tool.resultDisplay.metadata.documentPreview.wordCount, isMarkdown: tool.resultDisplay.metadata.documentPreview.isMarkdown, isExpanded: isExpanded, content: docContent ?? undefined, isLoading: isExpanded && !documentContents.has(docId), terminalWidth: terminalWidth }) }, `tool-${i}`));
|
|
235
|
+
}
|
|
236
|
+
// Show simple tool result for non-Edit/Write tools
|
|
237
|
+
return (_jsxs(Box, { children: [_jsxs(Text, { color: isSuccess ? Colors.AccentGreen : Colors.AccentRed, children: [isSuccess ? '✓' : '✗', ' '] }), _jsx(Text, { bold: true, children: tool.name }), filePath && _jsxs(Text, { dimColor: true, children: [" (", filePath, ")"] })] }, `tool-${i}`));
|
|
238
|
+
}) }));
|
|
239
|
+
}
|
|
240
|
+
default:
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
// Display name for debugging
|
|
245
|
+
MessageDisplay.displayName = 'MessageDisplay';
|
|
246
|
+
/**
|
|
247
|
+
* Enhanced Input component with proper cursor control and paste support
|
|
248
|
+
*
|
|
249
|
+
* Uses our SimpleInputBuffer for standalone input handling:
|
|
250
|
+
* - Full cursor control: Left/Right for chars, Up/Down for lines
|
|
251
|
+
* - Multi-line input: Ctrl+J adds new line, Enter submits
|
|
252
|
+
* - Input history: Up/Down arrows navigate previous messages (when single-line)
|
|
253
|
+
* - Slash command autocomplete: Shows suggestions when typing /
|
|
254
|
+
* - Paste support via bracketed paste mode (raw stdin parsing)
|
|
255
|
+
* - Visual cursor with inverse highlight
|
|
256
|
+
*
|
|
257
|
+
* Wrapped in React.memo to prevent re-renders from parent state changes
|
|
258
|
+
*/
|
|
259
|
+
const EnhancedInput = React.memo(({ onSubmit, disabled, placeholder = 'Type your message...', userMessages, prefillValue }) => {
|
|
260
|
+
const { width: terminalWidth } = useTerminalSize();
|
|
261
|
+
const { stdin } = useStdin();
|
|
262
|
+
// Use our simple input buffer (state is managed internally)
|
|
263
|
+
const bufferRef = useRef(null);
|
|
264
|
+
if (!bufferRef.current) {
|
|
265
|
+
bufferRef.current = createInputBuffer();
|
|
266
|
+
}
|
|
267
|
+
const buffer = bufferRef.current;
|
|
268
|
+
// Force re-render when buffer changes
|
|
269
|
+
const [, forceUpdate] = useState({});
|
|
270
|
+
const triggerUpdate = useCallback(() => forceUpdate({}), []);
|
|
271
|
+
// Ghost text (prediction autocomplete shown as dim text after cursor)
|
|
272
|
+
const [ghostText, setGhostText] = useState(null);
|
|
273
|
+
const lastPrefillRef = useRef(null);
|
|
274
|
+
// §5.1 prefill provenance for the CURRENT composition (reset on submit):
|
|
275
|
+
// ghostSeenRef — a ghost was rendered at any point since the last submit
|
|
276
|
+
// (even if later dismissed by typing/Escape): submission = 'shown'.
|
|
277
|
+
// ghostInsertedRef — the user Tab/→-accepted the ghost (edits after
|
|
278
|
+
// insertion don't clear it — anchoring already happened): 'inserted'.
|
|
279
|
+
const ghostSeenRef = useRef(false);
|
|
280
|
+
const ghostInsertedRef = useRef(false);
|
|
281
|
+
useEffect(() => {
|
|
282
|
+
if (prefillValue && prefillValue !== lastPrefillRef.current) {
|
|
283
|
+
setGhostText(prefillValue);
|
|
284
|
+
ghostSeenRef.current = true;
|
|
285
|
+
lastPrefillRef.current = prefillValue;
|
|
286
|
+
}
|
|
287
|
+
else if (!prefillValue) {
|
|
288
|
+
setGhostText(null);
|
|
289
|
+
lastPrefillRef.current = null;
|
|
290
|
+
}
|
|
291
|
+
}, [prefillValue]);
|
|
292
|
+
// Input history state
|
|
293
|
+
const [historyIndex, setHistoryIndex] = useState(-1);
|
|
294
|
+
const [originalValue, setOriginalValue] = useState('');
|
|
295
|
+
// Command suggestions state
|
|
296
|
+
const [suggestions, setSuggestions] = useState([]);
|
|
297
|
+
const [suggestionIndex, setSuggestionIndex] = useState(0);
|
|
298
|
+
// Derived state
|
|
299
|
+
const text = buffer.text;
|
|
300
|
+
const lines = buffer.lines;
|
|
301
|
+
const lineCount = lines.length;
|
|
302
|
+
const isMultiLine = lineCount > 1;
|
|
303
|
+
const [cursorRow, cursorCol] = buffer.cursor;
|
|
304
|
+
const showSuggestions = suggestions.length > 0 && text.startsWith('/');
|
|
305
|
+
// Update suggestions when text changes
|
|
306
|
+
useEffect(() => {
|
|
307
|
+
if (text.startsWith('/') && !isMultiLine) {
|
|
308
|
+
const newSuggestions = getSuggestions(text); // No limit - allow scrolling through all commands
|
|
309
|
+
setSuggestions(newSuggestions);
|
|
310
|
+
setSuggestionIndex(prev => prev >= newSuggestions.length ? Math.max(0, newSuggestions.length - 1) : prev);
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
setSuggestions([]);
|
|
314
|
+
setSuggestionIndex(0);
|
|
315
|
+
}
|
|
316
|
+
}, [text, isMultiLine]);
|
|
317
|
+
// Refs for current state in event handler
|
|
318
|
+
const stateRef = useRef({ historyIndex, originalValue, suggestions, suggestionIndex, text, cursorRow, lineCount });
|
|
319
|
+
stateRef.current = { historyIndex, originalValue, suggestions, suggestionIndex, text, cursorRow, lineCount };
|
|
320
|
+
// Flag to skip useInput when we've handled a paste
|
|
321
|
+
const skipNextInputRef = useRef(false);
|
|
322
|
+
// Enable bracketed paste mode when component mounts
|
|
323
|
+
useEffect(() => {
|
|
324
|
+
if (disabled)
|
|
325
|
+
return;
|
|
326
|
+
// Enable bracketed paste mode
|
|
327
|
+
process.stdout.write('\x1b[?2004h');
|
|
328
|
+
return () => {
|
|
329
|
+
// Disable bracketed paste mode on cleanup
|
|
330
|
+
process.stdout.write('\x1b[?2004l');
|
|
331
|
+
};
|
|
332
|
+
}, [disabled]);
|
|
333
|
+
// Raw stdin handling ONLY for bracketed paste detection
|
|
334
|
+
// Regular keys are handled by useInput below
|
|
335
|
+
useEffect(() => {
|
|
336
|
+
if (!stdin || disabled)
|
|
337
|
+
return;
|
|
338
|
+
const handleData = (data) => {
|
|
339
|
+
// Check for bracketed paste (escape sequence wrapped)
|
|
340
|
+
// Format: \x1b[200~ ... content ... \x1b[201~
|
|
341
|
+
if (data.includes('\x1b[200~')) {
|
|
342
|
+
const startIdx = data.indexOf('\x1b[200~');
|
|
343
|
+
const endIdx = data.indexOf('\x1b[201~');
|
|
344
|
+
if (endIdx !== -1) {
|
|
345
|
+
const pasteContent = data.slice(startIdx + 6, endIdx);
|
|
346
|
+
// Normalize line endings
|
|
347
|
+
const cleanedPaste = pasteContent
|
|
348
|
+
.replace(/\r\n/g, '\n')
|
|
349
|
+
.replace(/\r/g, '\n')
|
|
350
|
+
.replace(/\n+$/, '');
|
|
351
|
+
buffer.insert(cleanedPaste, { isPaste: true });
|
|
352
|
+
if (stateRef.current.historyIndex !== -1) {
|
|
353
|
+
setHistoryIndex(-1);
|
|
354
|
+
}
|
|
355
|
+
// Set flag to skip useInput processing for a short time
|
|
356
|
+
// This prevents the double-processing issue
|
|
357
|
+
skipNextInputRef.current = true;
|
|
358
|
+
setTimeout(() => { skipNextInputRef.current = false; }, 50);
|
|
359
|
+
triggerUpdate();
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// Heuristic: If data contains multiple characters including newlines,
|
|
364
|
+
// and it doesn't start with escape sequences, treat it as a paste.
|
|
365
|
+
const looksLikePaste = data.length > 1 &&
|
|
366
|
+
!data.startsWith('\x1b') &&
|
|
367
|
+
(data.includes('\n') || data.includes('\r')) &&
|
|
368
|
+
data.replace(/[\r\n]/g, '').length > 0;
|
|
369
|
+
if (looksLikePaste) {
|
|
370
|
+
const cleanedPaste = data
|
|
371
|
+
.replace(/\r\n/g, '\n')
|
|
372
|
+
.replace(/\r/g, '\n')
|
|
373
|
+
.replace(/\n+$/, '');
|
|
374
|
+
buffer.insert(cleanedPaste, { isPaste: true });
|
|
375
|
+
if (stateRef.current.historyIndex !== -1) {
|
|
376
|
+
setHistoryIndex(-1);
|
|
377
|
+
}
|
|
378
|
+
skipNextInputRef.current = true;
|
|
379
|
+
setTimeout(() => { skipNextInputRef.current = false; }, 50);
|
|
380
|
+
triggerUpdate();
|
|
381
|
+
}
|
|
382
|
+
// All other input is handled by useInput below
|
|
383
|
+
};
|
|
384
|
+
stdin.on('data', handleData);
|
|
385
|
+
return () => {
|
|
386
|
+
stdin.removeListener('data', handleData);
|
|
387
|
+
};
|
|
388
|
+
}, [stdin, disabled, buffer, triggerUpdate]);
|
|
389
|
+
// Use Ink's useInput for all keyboard handling (it properly parses escape sequences)
|
|
390
|
+
useInput((input, key) => {
|
|
391
|
+
if (disabled)
|
|
392
|
+
return;
|
|
393
|
+
// Skip if we just handled a paste via raw stdin
|
|
394
|
+
if (skipNextInputRef.current)
|
|
395
|
+
return;
|
|
396
|
+
const state = stateRef.current;
|
|
397
|
+
const showingSuggestions = state.suggestions.length > 0 && buffer.text.startsWith('/');
|
|
398
|
+
// Submit on Enter
|
|
399
|
+
if (key.return) {
|
|
400
|
+
if (showingSuggestions) {
|
|
401
|
+
const selected = state.suggestions[state.suggestionIndex];
|
|
402
|
+
if (selected) {
|
|
403
|
+
// Slash-command pick: control action, not a conversational turn —
|
|
404
|
+
// provenance 'none'; ghost refs deliberately NOT reset (a seen ghost
|
|
405
|
+
// can still anchor the next real message after e.g. /model).
|
|
406
|
+
onSubmit(selected.fullPath, 'none');
|
|
407
|
+
buffer.setText('');
|
|
408
|
+
setSuggestions([]);
|
|
409
|
+
setSuggestionIndex(0);
|
|
410
|
+
setHistoryIndex(-1);
|
|
411
|
+
setOriginalValue('');
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
else if (buffer.text.trim()) {
|
|
415
|
+
// §5.1 provenance of this submission relative to the ghost prefill
|
|
416
|
+
const provenance = ghostInsertedRef.current
|
|
417
|
+
? 'inserted'
|
|
418
|
+
: ghostSeenRef.current ? 'shown' : 'none';
|
|
419
|
+
ghostSeenRef.current = false;
|
|
420
|
+
ghostInsertedRef.current = false;
|
|
421
|
+
onSubmit(buffer.text, provenance);
|
|
422
|
+
buffer.setText('');
|
|
423
|
+
setHistoryIndex(-1);
|
|
424
|
+
setOriginalValue('');
|
|
425
|
+
setSuggestions([]);
|
|
426
|
+
setSuggestionIndex(0);
|
|
427
|
+
if (ghostText)
|
|
428
|
+
setGhostText(null);
|
|
429
|
+
}
|
|
430
|
+
triggerUpdate();
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
// Tab handling
|
|
434
|
+
if (key.tab) {
|
|
435
|
+
if (key.shift) {
|
|
436
|
+
// Shift+Tab handled at app level
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (showingSuggestions) {
|
|
440
|
+
const selected = state.suggestions[state.suggestionIndex];
|
|
441
|
+
if (selected) {
|
|
442
|
+
buffer.setText(selected.fullPath + ' ');
|
|
443
|
+
setSuggestions([]);
|
|
444
|
+
setSuggestionIndex(0);
|
|
445
|
+
}
|
|
446
|
+
triggerUpdate();
|
|
447
|
+
}
|
|
448
|
+
else if (ghostText) {
|
|
449
|
+
buffer.setText(buffer.text + ghostText);
|
|
450
|
+
ghostInsertedRef.current = true;
|
|
451
|
+
setGhostText(null);
|
|
452
|
+
triggerUpdate();
|
|
453
|
+
}
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
// Up arrow
|
|
457
|
+
if (key.upArrow) {
|
|
458
|
+
if (showingSuggestions) {
|
|
459
|
+
setSuggestionIndex(i => Math.max(0, i - 1));
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
// At top line (or single line)
|
|
463
|
+
if (state.lineCount === 1 || state.cursorRow === 0) {
|
|
464
|
+
// If cursor is not at beginning, first move to beginning
|
|
465
|
+
if (buffer.cursor[1] > 0) {
|
|
466
|
+
buffer.moveHome();
|
|
467
|
+
triggerUpdate();
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
// Cursor already at beginning - scroll through history
|
|
471
|
+
if (userMessages.length === 0)
|
|
472
|
+
return;
|
|
473
|
+
if (state.historyIndex === -1) {
|
|
474
|
+
setOriginalValue(buffer.text);
|
|
475
|
+
setHistoryIndex(0);
|
|
476
|
+
const newText = userMessages[userMessages.length - 1] || '';
|
|
477
|
+
buffer.setText(newText);
|
|
478
|
+
}
|
|
479
|
+
else if (state.historyIndex < userMessages.length - 1) {
|
|
480
|
+
const newIndex = state.historyIndex + 1;
|
|
481
|
+
setHistoryIndex(newIndex);
|
|
482
|
+
const newText = userMessages[userMessages.length - 1 - newIndex] || '';
|
|
483
|
+
buffer.setText(newText);
|
|
484
|
+
}
|
|
485
|
+
triggerUpdate();
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
// Move cursor up in multiline
|
|
489
|
+
buffer.moveUp();
|
|
490
|
+
triggerUpdate();
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
// Down arrow
|
|
494
|
+
if (key.downArrow) {
|
|
495
|
+
if (showingSuggestions) {
|
|
496
|
+
setSuggestionIndex(i => Math.min(state.suggestions.length - 1, i + 1));
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
// At bottom line (or single line)
|
|
500
|
+
if (state.lineCount === 1 || state.cursorRow === state.lineCount - 1) {
|
|
501
|
+
const currentLineLength = buffer.lines[buffer.cursor[0]]?.length || 0;
|
|
502
|
+
// If cursor is not at end, first move to end
|
|
503
|
+
if (buffer.cursor[1] < currentLineLength) {
|
|
504
|
+
buffer.moveEnd();
|
|
505
|
+
triggerUpdate();
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
// Cursor already at end - scroll through history (if in history mode)
|
|
509
|
+
if (state.historyIndex === -1)
|
|
510
|
+
return;
|
|
511
|
+
if (state.historyIndex === 0) {
|
|
512
|
+
setHistoryIndex(-1);
|
|
513
|
+
buffer.setText(state.originalValue);
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
const newIndex = state.historyIndex - 1;
|
|
517
|
+
setHistoryIndex(newIndex);
|
|
518
|
+
const newText = userMessages[userMessages.length - 1 - newIndex] || '';
|
|
519
|
+
buffer.setText(newText);
|
|
520
|
+
}
|
|
521
|
+
triggerUpdate();
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
// Move cursor down in multiline
|
|
525
|
+
buffer.moveDown();
|
|
526
|
+
triggerUpdate();
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
// Left arrow
|
|
530
|
+
if (key.leftArrow) {
|
|
531
|
+
if (key.ctrl) {
|
|
532
|
+
buffer.moveWordLeft();
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
buffer.moveLeft();
|
|
536
|
+
}
|
|
537
|
+
triggerUpdate();
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
// Right arrow
|
|
541
|
+
if (key.rightArrow) {
|
|
542
|
+
const atEnd = buffer.cursor[0] === buffer.lines.length - 1
|
|
543
|
+
&& buffer.cursor[1] >= (buffer.lines[buffer.cursor[0]] || '').length;
|
|
544
|
+
if (atEnd && ghostText) {
|
|
545
|
+
buffer.setText(buffer.text + ghostText);
|
|
546
|
+
ghostInsertedRef.current = true;
|
|
547
|
+
setGhostText(null);
|
|
548
|
+
}
|
|
549
|
+
else if (key.ctrl) {
|
|
550
|
+
buffer.moveWordRight();
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
buffer.moveRight();
|
|
554
|
+
}
|
|
555
|
+
triggerUpdate();
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
// Option+Delete (Alt+Backspace) - delete word to the left
|
|
559
|
+
// On Mac, Option+Delete sends \x17 (ETB) or the meta flag is set
|
|
560
|
+
if ((key.meta && (key.delete || key.backspace || input === '\x7f')) || input === '\x17') {
|
|
561
|
+
buffer.deleteWordLeft();
|
|
562
|
+
if (state.historyIndex !== -1)
|
|
563
|
+
setHistoryIndex(-1);
|
|
564
|
+
triggerUpdate();
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
// Mac Delete key (⌫) - deletes character to the LEFT of cursor
|
|
568
|
+
// Ink sets key.delete=true for Mac Delete key, so treat ALL delete as backspace
|
|
569
|
+
if (key.delete || key.backspace || input === '\x7f' || input === '\b') {
|
|
570
|
+
buffer.backspace();
|
|
571
|
+
if (state.historyIndex !== -1)
|
|
572
|
+
setHistoryIndex(-1);
|
|
573
|
+
triggerUpdate();
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
// Escape
|
|
577
|
+
if (key.escape) {
|
|
578
|
+
if (ghostText) {
|
|
579
|
+
setGhostText(null);
|
|
580
|
+
}
|
|
581
|
+
else if (showingSuggestions) {
|
|
582
|
+
setSuggestions([]);
|
|
583
|
+
setSuggestionIndex(0);
|
|
584
|
+
}
|
|
585
|
+
else if (state.historyIndex !== -1) {
|
|
586
|
+
setHistoryIndex(-1);
|
|
587
|
+
buffer.setText(state.originalValue);
|
|
588
|
+
}
|
|
589
|
+
else if (buffer.text) {
|
|
590
|
+
buffer.setText('');
|
|
591
|
+
}
|
|
592
|
+
triggerUpdate();
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
// Ctrl+J for newline
|
|
596
|
+
if (key.ctrl && input === 'j') {
|
|
597
|
+
buffer.newline();
|
|
598
|
+
if (state.historyIndex !== -1)
|
|
599
|
+
setHistoryIndex(-1);
|
|
600
|
+
triggerUpdate();
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
// Ctrl+A - move to start
|
|
604
|
+
if (key.ctrl && input === 'a') {
|
|
605
|
+
buffer.moveHome();
|
|
606
|
+
triggerUpdate();
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
// Ctrl+E - move to end
|
|
610
|
+
if (key.ctrl && input === 'e') {
|
|
611
|
+
buffer.moveEnd();
|
|
612
|
+
triggerUpdate();
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
// Ctrl+U - kill to start of line
|
|
616
|
+
if (key.ctrl && input === 'u') {
|
|
617
|
+
while (buffer.cursor[1] > 0) {
|
|
618
|
+
buffer.backspace();
|
|
619
|
+
}
|
|
620
|
+
if (state.historyIndex !== -1)
|
|
621
|
+
setHistoryIndex(-1);
|
|
622
|
+
triggerUpdate();
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
// Ctrl+K - kill to end of line
|
|
626
|
+
if (key.ctrl && input === 'k') {
|
|
627
|
+
const currentLine = buffer.lines[buffer.cursor[0]] || '';
|
|
628
|
+
while (buffer.cursor[1] < currentLine.length) {
|
|
629
|
+
buffer.delete();
|
|
630
|
+
}
|
|
631
|
+
if (state.historyIndex !== -1)
|
|
632
|
+
setHistoryIndex(-1);
|
|
633
|
+
triggerUpdate();
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
// Ctrl+Z - undo
|
|
637
|
+
if (key.ctrl && input === 'z') {
|
|
638
|
+
buffer.undo();
|
|
639
|
+
triggerUpdate();
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
// Regular character input (not control keys)
|
|
643
|
+
if (input && !key.ctrl && !key.meta) {
|
|
644
|
+
if (ghostText)
|
|
645
|
+
setGhostText(null);
|
|
646
|
+
buffer.insert(input);
|
|
647
|
+
if (state.historyIndex !== -1)
|
|
648
|
+
setHistoryIndex(-1);
|
|
649
|
+
triggerUpdate();
|
|
650
|
+
}
|
|
651
|
+
}, { isActive: !disabled });
|
|
652
|
+
// Render text with cursor highlighting
|
|
653
|
+
const renderContent = () => {
|
|
654
|
+
if (!text) {
|
|
655
|
+
if (ghostText && !disabled) {
|
|
656
|
+
// Empty buffer with ghost text: show cursor then dim prediction
|
|
657
|
+
return (_jsxs(Text, { children: [_jsx(Text, { inverse: true, children: ghostText.charAt(0) }), _jsx(Text, { dimColor: true, children: ghostText.slice(1) })] }));
|
|
658
|
+
}
|
|
659
|
+
// When showing placeholder: show cursor position with first char highlighted
|
|
660
|
+
const displayPlaceholder = placeholder || 'Type your message...';
|
|
661
|
+
return (_jsxs(Text, { children: [!disabled && _jsx(Text, { inverse: true, children: displayPlaceholder.charAt(0) || ' ' }), _jsx(Text, { dimColor: true, children: disabled ? displayPlaceholder : displayPlaceholder.slice(1) })] }));
|
|
662
|
+
}
|
|
663
|
+
const isLastLine = (idx) => idx === lines.length - 1;
|
|
664
|
+
const showGhostOnLine = (idx) => ghostText && !disabled && isLastLine(idx) && idx === cursorRow
|
|
665
|
+
&& cursorCol >= (lines[idx] || '').length;
|
|
666
|
+
return (_jsx(Box, { flexDirection: "column", children: lines.map((line, lineIdx) => {
|
|
667
|
+
const isOnCursorLine = lineIdx === cursorRow;
|
|
668
|
+
if (!isOnCursorLine || disabled) {
|
|
669
|
+
return (_jsx(Text, { color: disabled ? Colors.Gray : undefined, children: line || ' ' }, lineIdx));
|
|
670
|
+
}
|
|
671
|
+
// Render line with cursor
|
|
672
|
+
const lineLen = line.length;
|
|
673
|
+
const before = line.slice(0, cursorCol);
|
|
674
|
+
const cursorChar = cursorCol < lineLen ? line.slice(cursorCol, cursorCol + 1) : ' ';
|
|
675
|
+
const after = cursorCol < lineLen ? line.slice(cursorCol + 1) : '';
|
|
676
|
+
return (_jsxs(Text, { children: [before, _jsx(Text, { inverse: true, children: cursorChar }), after, showGhostOnLine(lineIdx) && _jsx(Text, { dimColor: true, children: ghostText })] }, lineIdx));
|
|
677
|
+
}) }));
|
|
678
|
+
};
|
|
679
|
+
// Determine border color based on mode
|
|
680
|
+
const getBorderColor = () => {
|
|
681
|
+
if (disabled)
|
|
682
|
+
return Colors.Gray;
|
|
683
|
+
if (showSuggestions)
|
|
684
|
+
return Colors.AccentGreen;
|
|
685
|
+
if (historyIndex !== -1)
|
|
686
|
+
return Colors.AccentYellow;
|
|
687
|
+
return Colors.AccentCyan;
|
|
688
|
+
};
|
|
689
|
+
return (_jsxs(Box, { flexDirection: "column", children: [showSuggestions && (_jsx(CommandSuggestions, { suggestions: suggestions, selectedIndex: suggestionIndex, maxVisible: 8 })), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: getBorderColor(), children: '─'.repeat(Math.max(terminalWidth - 2, 40)) }), _jsx(Box, { paddingX: 1, minHeight: 1, children: renderContent() }), _jsx(Text, { color: getBorderColor(), children: '─'.repeat(Math.max(terminalWidth - 2, 40)) })] }), _jsxs(Box, { paddingX: 1, children: [showSuggestions && (_jsx(Text, { dimColor: true, color: Colors.AccentGreen, children: "\u2191\u2193 select \u2022 Tab complete \u2022 Enter execute \u2022 Esc close" })), !showSuggestions && historyIndex !== -1 && (_jsxs(Text, { dimColor: true, color: Colors.AccentYellow, children: ["History [", historyIndex + 1, "/", userMessages.length, "] \u2022 \u2193 newer \u2022 Esc cancel"] })), !showSuggestions && isMultiLine && historyIndex === -1 && (_jsxs(Text, { dimColor: true, children: [lineCount, " lines \u2022 \u2190\u2192\u2191\u2193 cursor \u2022 Ctrl+A/E home/end \u2022 Enter submit"] })), !showSuggestions && !isMultiLine && historyIndex === -1 && !ghostText && (_jsx(Text, { dimColor: true, children: "/ commands \u2022 \u2191\u2193 history \u2022 Ctrl+J new line \u2022 \u2190\u2192 cursor" })), ghostText && !showSuggestions && historyIndex === -1 && (_jsx(Text, { dimColor: true, children: "Tab or \u2192 accept prediction \u2022 Esc dismiss \u2022 type to replace" }))] })] }));
|
|
690
|
+
});
|
|
691
|
+
// Display name for debugging
|
|
692
|
+
EnhancedInput.displayName = 'EnhancedInput';
|
|
693
|
+
/**
|
|
694
|
+
* Loading spinner component
|
|
695
|
+
*/
|
|
696
|
+
const LoadingSpinner = ({ text = 'Thinking...' }) => {
|
|
697
|
+
const [frame, setFrame] = useState(0);
|
|
698
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
699
|
+
useEffect(() => {
|
|
700
|
+
const timer = setInterval(() => {
|
|
701
|
+
setFrame(f => (f + 1) % frames.length);
|
|
702
|
+
}, 80);
|
|
703
|
+
return () => clearInterval(timer);
|
|
704
|
+
}, []);
|
|
705
|
+
return (_jsxs(Text, { color: Colors.AccentCyan, children: [frames[frame], " ", text] }));
|
|
706
|
+
};
|
|
707
|
+
/**
|
|
708
|
+
* Theme Picker Dialog Component
|
|
709
|
+
*
|
|
710
|
+
* Interactive theme selection with live preview colors
|
|
711
|
+
*/
|
|
712
|
+
const ThemePickerDialog = ({ themes, currentTheme, onSelect, onCancel }) => {
|
|
713
|
+
const [selectedIndex, setSelectedIndex] = useState(() => Math.max(0, themes.indexOf(currentTheme)));
|
|
714
|
+
const selectedTheme = themes[selectedIndex] || themes[0];
|
|
715
|
+
const themeDef = selectedTheme ? getThemeDefinition(selectedTheme) : null;
|
|
716
|
+
useInput((input, key) => {
|
|
717
|
+
// Up/Down to navigate
|
|
718
|
+
if (key.upArrow) {
|
|
719
|
+
setSelectedIndex(i => Math.max(0, i - 1));
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
if (key.downArrow) {
|
|
723
|
+
setSelectedIndex(i => Math.min(themes.length - 1, i + 1));
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
// Number keys for quick selection (1-9)
|
|
727
|
+
const num = parseInt(input, 10);
|
|
728
|
+
if (num >= 1 && num <= 9 && num <= themes.length) {
|
|
729
|
+
setSelectedIndex(num - 1);
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
// Enter to select
|
|
733
|
+
if (key.return && selectedTheme) {
|
|
734
|
+
onSelect(selectedTheme);
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
// Escape to cancel
|
|
738
|
+
if (key.escape) {
|
|
739
|
+
onCancel();
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: Colors.AccentPurple, paddingX: 2, paddingY: 1, marginY: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: Colors.AccentPurple, bold: true, children: "Select Theme" }) }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { flexDirection: "column", width: "50%", children: themes.map((theme, index) => {
|
|
744
|
+
const def = getThemeDefinition(theme);
|
|
745
|
+
const isSelected = index === selectedIndex;
|
|
746
|
+
const isCurrent = theme === currentTheme;
|
|
747
|
+
return (_jsx(Box, { children: _jsxs(Text, { color: isSelected ? Colors.AccentCyan : undefined, bold: isSelected, children: [isSelected ? '❯' : ' ', " ", index < 9 ? `${index + 1}.` : ' ', ' ', _jsx(Text, { color: isSelected ? Colors.AccentCyan : (isCurrent ? Colors.AccentGreen : undefined), children: def?.name || theme }), isCurrent && _jsx(Text, { dimColor: true, children: " (current)" })] }) }, theme));
|
|
748
|
+
}) }), themeDef && (_jsxs(Box, { flexDirection: "column", width: "50%", paddingLeft: 2, children: [_jsx(Text, { bold: true, color: Colors.AccentPurple, children: "Preview" }), _jsxs(Box, { borderStyle: "single", borderColor: themeDef.dimmed, flexDirection: "column", paddingX: 1, marginTop: 1, children: [_jsx(Text, { color: themeDef.primary, children: "\u25CF Primary" }), _jsx(Text, { color: themeDef.secondary, children: "\u25CF Secondary" }), _jsx(Text, { color: themeDef.success, children: "\u25CF Success" }), _jsx(Text, { color: themeDef.warning, children: "\u25CF Warning" }), _jsx(Text, { color: themeDef.error, children: "\u25CF Error" }), _jsx(Text, { color: themeDef.info, children: "\u25CF Info" }), _jsx(Text, { color: themeDef.text, children: "\u25CF Text" }), _jsx(Text, { color: themeDef.dimmed, children: "\u25CF Dimmed" })] })] }))] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "\u2191\u2193 navigate \u2022 1-9 quick select \u2022 Enter apply \u2022 Esc cancel" }) })] }));
|
|
749
|
+
};
|
|
750
|
+
/**
|
|
751
|
+
* Format relative time for session display
|
|
752
|
+
*/
|
|
753
|
+
function formatSessionAge(date) {
|
|
754
|
+
const now = new Date();
|
|
755
|
+
const diffMs = now.getTime() - date.getTime();
|
|
756
|
+
const diffMins = Math.floor(diffMs / (1000 * 60));
|
|
757
|
+
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
758
|
+
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
759
|
+
if (diffMins < 1)
|
|
760
|
+
return 'Just now';
|
|
761
|
+
if (diffMins < 60)
|
|
762
|
+
return `${diffMins}m ago`;
|
|
763
|
+
if (diffHours < 24)
|
|
764
|
+
return `${diffHours}h ago`;
|
|
765
|
+
if (diffDays === 1)
|
|
766
|
+
return 'Yesterday';
|
|
767
|
+
if (diffDays < 7)
|
|
768
|
+
return `${diffDays}d ago`;
|
|
769
|
+
if (diffDays < 30)
|
|
770
|
+
return `${Math.floor(diffDays / 7)}w ago`;
|
|
771
|
+
return date.toLocaleDateString();
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Session Picker Dialog Component
|
|
775
|
+
*
|
|
776
|
+
* Interactive session selection with details panel, similar to ModelPickerDialog.
|
|
777
|
+
*/
|
|
778
|
+
const SessionPickerDialog = ({ sessions, onSelect, onCancel, loading = false }) => {
|
|
779
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
780
|
+
// Memoize to prevent recalculation
|
|
781
|
+
const selectedSession = useMemo(() => sessions[selectedIndex] || sessions[0], [sessions, selectedIndex]);
|
|
782
|
+
useInput((input, key) => {
|
|
783
|
+
if (loading)
|
|
784
|
+
return;
|
|
785
|
+
if (key.upArrow) {
|
|
786
|
+
setSelectedIndex(i => Math.max(0, i - 1));
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
if (key.downArrow) {
|
|
790
|
+
setSelectedIndex(i => Math.min(sessions.length - 1, i + 1));
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
const num = parseInt(input, 10);
|
|
794
|
+
if (num >= 1 && num <= 9 && num <= sessions.length) {
|
|
795
|
+
setSelectedIndex(num - 1);
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
if (key.return && selectedSession) {
|
|
799
|
+
onSelect(selectedSession.sessionId);
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
if (key.escape) {
|
|
803
|
+
onCancel();
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
});
|
|
807
|
+
// Fixed window size - always show exactly this many rows
|
|
808
|
+
const VISIBLE_ROWS = 10;
|
|
809
|
+
// Calculate scroll window with stable bounds
|
|
810
|
+
const scrollWindow = useMemo(() => {
|
|
811
|
+
const total = sessions.length;
|
|
812
|
+
if (total <= VISIBLE_ROWS) {
|
|
813
|
+
return { start: 0, end: total };
|
|
814
|
+
}
|
|
815
|
+
// Keep selection centered when possible
|
|
816
|
+
const halfWindow = Math.floor(VISIBLE_ROWS / 2);
|
|
817
|
+
let start = selectedIndex - halfWindow;
|
|
818
|
+
let end = selectedIndex + halfWindow + (VISIBLE_ROWS % 2);
|
|
819
|
+
// Clamp to bounds
|
|
820
|
+
if (start < 0) {
|
|
821
|
+
start = 0;
|
|
822
|
+
end = VISIBLE_ROWS;
|
|
823
|
+
}
|
|
824
|
+
else if (end > total) {
|
|
825
|
+
end = total;
|
|
826
|
+
start = total - VISIBLE_ROWS;
|
|
827
|
+
}
|
|
828
|
+
return { start, end };
|
|
829
|
+
}, [sessions.length, selectedIndex]);
|
|
830
|
+
// Build display rows - memoized
|
|
831
|
+
const displayRows = useMemo(() => {
|
|
832
|
+
const rows = [];
|
|
833
|
+
for (let i = scrollWindow.start; i < scrollWindow.end; i++) {
|
|
834
|
+
const session = sessions[i];
|
|
835
|
+
if (session) {
|
|
836
|
+
rows.push({
|
|
837
|
+
key: `row-${i}-${session.sessionId}`,
|
|
838
|
+
session,
|
|
839
|
+
index: i,
|
|
840
|
+
isSelected: i === selectedIndex,
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
return rows;
|
|
845
|
+
}, [sessions, scrollWindow, selectedIndex]);
|
|
846
|
+
const aboveCount = scrollWindow.start;
|
|
847
|
+
const belowCount = sessions.length - scrollWindow.end;
|
|
848
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: Colors.AccentPurple, paddingX: 2, paddingY: 1, marginY: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: Colors.AccentPurple, bold: true, children: ["Resume Session (", sessions.length, " available)"] }), loading && _jsx(Text, { dimColor: true, children: " loading..." })] }), loading ? (_jsx(Box, { height: VISIBLE_ROWS + 2, children: _jsx(Text, { dimColor: true, children: "Loading sessions..." }) })) : sessions.length === 0 ? (_jsx(Box, { height: 3, children: _jsx(Text, { dimColor: true, children: "No previous sessions found." }) })) : (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Box, { flexDirection: "column", width: 44, children: [aboveCount > 0 && (_jsxs(Text, { dimColor: true, children: [" \u2191 ", aboveCount, " more"] })), aboveCount === 0 && _jsx(Text, { children: " " }), displayRows.map((row) => (_jsxs(Text, { children: [_jsx(Text, { color: row.isSelected ? Colors.AccentCyan : undefined, bold: row.isSelected, children: row.isSelected ? '❯ ' : ' ' }), _jsxs(Text, { dimColor: true, children: [row.index + 1, ". "] }), _jsx(Text, { color: row.isSelected ? Colors.AccentCyan : Colors.AccentBlue, children: row.session.title
|
|
849
|
+
? row.session.title.slice(0, 28).padEnd(28)
|
|
850
|
+
: row.session.shortId.padEnd(28) }), _jsx(Text, { dimColor: true, children: " \u2502 " }), _jsx(Text, { color: row.isSelected ? Colors.AccentCyan : undefined, children: row.session.age.padEnd(9) })] }, row.key))), belowCount > 0 && (_jsxs(Text, { dimColor: true, children: [" \u2193 ", belowCount, " more"] })), belowCount === 0 && _jsx(Text, { children: " " })] }), _jsxs(Box, { flexDirection: "column", width: 38, paddingLeft: 1, children: [_jsx(Text, { bold: true, color: Colors.AccentPurple, children: "Session Details" }), _jsx(Box, { borderStyle: "single", borderColor: Colors.Gray, flexDirection: "column", paddingX: 1, height: 13, children: selectedSession && (_jsxs(_Fragment, { children: [selectedSession.title && (_jsx(Text, { bold: true, color: Colors.AccentCyan, children: selectedSession.title.slice(0, 34) })), _jsxs(Text, { children: [_jsx(Text, { color: Colors.AccentCyan, children: "ID:" }), " ", selectedSession.shortId] }), _jsxs(Text, { children: [_jsx(Text, { color: Colors.AccentCyan, children: "Messages:" }), " ", selectedSession.messageCount] }), _jsxs(Text, { children: [_jsx(Text, { color: Colors.AccentCyan, children: "Last Active:" }), " ", selectedSession.age] }), _jsxs(Text, { children: [_jsx(Text, { color: Colors.AccentCyan, children: "Model:" }), " ", (selectedSession.model || 'Unknown').slice(0, 24)] }), _jsxs(Text, { children: [_jsx(Text, { color: Colors.AccentCyan, children: "Date:" }), " ", selectedSession.lastModified.toLocaleDateString()] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: Colors.AccentCyan, children: "First Prompt:" }), _jsx(Text, { dimColor: true, wrap: "wrap", children: selectedSession.firstUserMessage
|
|
851
|
+
? selectedSession.firstUserMessage.slice(0, 250)
|
|
852
|
+
: '(No preview available)' })] })] })) })] })] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "\u2191\u2193 navigate \u2022 1-9 quick select \u2022 Enter resume \u2022 Esc cancel" }) })] }));
|
|
853
|
+
};
|
|
854
|
+
/**
|
|
855
|
+
* Main Cortex Application
|
|
856
|
+
*/
|
|
857
|
+
export const CortexApp = ({ modelId, debug = false, projectPath, autoApprove: initialAutoApprove = false, initialPrompt, }) => {
|
|
858
|
+
const { exit } = useApp();
|
|
859
|
+
useStdout(); // Keep for potential future use
|
|
860
|
+
const { width: terminalWidth, height: terminalHeight } = useTerminalSize();
|
|
861
|
+
// Application state
|
|
862
|
+
const [client, setClient] = useState(null);
|
|
863
|
+
const [initialized, setInitialized] = useState(false);
|
|
864
|
+
const [initError, setInitError] = useState(null);
|
|
865
|
+
const [showHelp, setShowHelp] = useState(false);
|
|
866
|
+
const [showConfigMenu, setShowConfigMenu] = useState(false);
|
|
867
|
+
const [showThinking, setShowThinking] = useState(false);
|
|
868
|
+
const [autoApprove, setAutoApprove] = useState(initialAutoApprove);
|
|
869
|
+
const [currentModel, setCurrentModel] = useState(modelId || 'loading...');
|
|
870
|
+
const [reasoningEffort, setReasoningEffort] = useState('none');
|
|
871
|
+
const [supportsReasoning, setSupportsReasoning] = useState(false);
|
|
872
|
+
const [reasoningToggleable, setReasoningToggleable] = useState(false);
|
|
873
|
+
// Initialize theme from .cortex/config.json or use default
|
|
874
|
+
const [currentTheme, setCurrentTheme] = useState(() => initializeTheme());
|
|
875
|
+
const [showThemePicker, setShowThemePicker] = useState(false);
|
|
876
|
+
// Model picker state
|
|
877
|
+
const [showModelPicker, setShowModelPicker] = useState(false);
|
|
878
|
+
const [availableModels, setAvailableModels] = useState([]);
|
|
879
|
+
const [modelsLoading, setModelsLoading] = useState(false);
|
|
880
|
+
// Session picker state
|
|
881
|
+
const [showSessionPicker, setShowSessionPicker] = useState(false);
|
|
882
|
+
const [availableSessions, setAvailableSessions] = useState([]);
|
|
883
|
+
const [sessionsLoading, setSessionsLoading] = useState(false);
|
|
884
|
+
// System message manager state
|
|
885
|
+
const [showSystemMessageManager, setShowSystemMessageManager] = useState(false);
|
|
886
|
+
const [systemMessageStore, setSystemMessageStore] = useState(null);
|
|
887
|
+
// Mentorship config menu state
|
|
888
|
+
const [showMentorshipConfig, setShowMentorshipConfig] = useState(false);
|
|
889
|
+
const [mentorshipMenuDef, setMentorshipMenuDef] = useState(null);
|
|
890
|
+
const [mentorshipInitialValues, setMentorshipInitialValues] = useState({});
|
|
891
|
+
const [mentorshipService, setMentorshipService] = useState(null);
|
|
892
|
+
// Sub-agent tracking state for parallel agent display
|
|
893
|
+
const subAgentStateManagerRef = useRef(createSubAgentStateManager());
|
|
894
|
+
const [subAgents, setSubAgents] = useState(new Map());
|
|
895
|
+
// Sub-agent event handler callback - updates state for UI
|
|
896
|
+
const handleSubAgentEvent = useCallback((event) => {
|
|
897
|
+
const manager = subAgentStateManagerRef.current;
|
|
898
|
+
switch (event.type) {
|
|
899
|
+
case 'started':
|
|
900
|
+
manager.handleStarted({
|
|
901
|
+
agentId: event.agentId,
|
|
902
|
+
agentName: event.agentName,
|
|
903
|
+
model: event.data.model || 'unknown',
|
|
904
|
+
});
|
|
905
|
+
break;
|
|
906
|
+
case 'progress':
|
|
907
|
+
manager.handleProgress({
|
|
908
|
+
agentId: event.agentId,
|
|
909
|
+
turnNumber: event.data.turnNumber || 0,
|
|
910
|
+
totalTokens: event.data.totalTokens || 0,
|
|
911
|
+
elapsedMs: event.data.elapsedMs || 0,
|
|
912
|
+
});
|
|
913
|
+
break;
|
|
914
|
+
case 'tool_call':
|
|
915
|
+
manager.handleToolCall({
|
|
916
|
+
agentId: event.agentId,
|
|
917
|
+
toolName: event.data.toolName || 'unknown',
|
|
918
|
+
toolInput: {},
|
|
919
|
+
});
|
|
920
|
+
break;
|
|
921
|
+
case 'text':
|
|
922
|
+
manager.handleText({
|
|
923
|
+
agentId: event.agentId,
|
|
924
|
+
text: event.data.text || '',
|
|
925
|
+
isFinal: event.data.isFinal || false,
|
|
926
|
+
});
|
|
927
|
+
break;
|
|
928
|
+
case 'completed':
|
|
929
|
+
manager.handleCompleted({
|
|
930
|
+
agentId: event.agentId,
|
|
931
|
+
result: {
|
|
932
|
+
status: event.data.status || 'completed',
|
|
933
|
+
durationMs: event.data.elapsedMs || 0,
|
|
934
|
+
turnCount: event.data.turnNumber || 0,
|
|
935
|
+
},
|
|
936
|
+
});
|
|
937
|
+
// Clear completed agent from display after brief delay
|
|
938
|
+
// This allows the completion status to show briefly, then it scrolls away
|
|
939
|
+
setTimeout(() => {
|
|
940
|
+
manager.agents.delete(event.agentId);
|
|
941
|
+
setSubAgents(new Map(manager.agents));
|
|
942
|
+
}, 2000);
|
|
943
|
+
break;
|
|
944
|
+
case 'error':
|
|
945
|
+
manager.handleError({
|
|
946
|
+
agentId: event.agentId,
|
|
947
|
+
agentName: event.agentName,
|
|
948
|
+
error: new Error(event.data.error || 'Unknown error'),
|
|
949
|
+
});
|
|
950
|
+
// Clear errored agent from display after delay
|
|
951
|
+
setTimeout(() => {
|
|
952
|
+
manager.agents.delete(event.agentId);
|
|
953
|
+
setSubAgents(new Map(manager.agents));
|
|
954
|
+
}, 3000);
|
|
955
|
+
break;
|
|
956
|
+
}
|
|
957
|
+
// Trigger re-render with updated agents
|
|
958
|
+
setSubAgents(new Map(manager.agents));
|
|
959
|
+
}, []);
|
|
960
|
+
// Queued/staged message - submitted while streaming, will auto-send when ready
|
|
961
|
+
const [queuedMessage, setQueuedMessage] = useState(null);
|
|
962
|
+
// History management - read directly from orchestrator (single source of truth)
|
|
963
|
+
const [orchestratorHistory, setOrchestratorHistory] = useState([]);
|
|
964
|
+
// Sync history from orchestrator (used for real-time updates during streaming)
|
|
965
|
+
// TODO: Wire this into streaming to show real-time tool execution
|
|
966
|
+
const refreshOrchestratorHistory = useCallback(() => {
|
|
967
|
+
if (client) {
|
|
968
|
+
const messages = client.getMessageHistory();
|
|
969
|
+
setOrchestratorHistory([...messages]);
|
|
970
|
+
}
|
|
971
|
+
}, [client]);
|
|
972
|
+
// Initialize orchestrator history on client ready
|
|
973
|
+
useEffect(() => {
|
|
974
|
+
if (client) {
|
|
975
|
+
refreshOrchestratorHistory();
|
|
976
|
+
}
|
|
977
|
+
}, [client, refreshOrchestratorHistory]);
|
|
978
|
+
// Legacy addItem for slash commands that add info/error messages
|
|
979
|
+
// These are UI-only messages, not part of the conversation
|
|
980
|
+
const [uiMessages, setUiMessages] = useState([]);
|
|
981
|
+
const addItem = useCallback((item, timestamp, _isResuming) => {
|
|
982
|
+
const id = `ui-${timestamp}-${Math.random().toString(36).substr(2, 5)}`;
|
|
983
|
+
setUiMessages(prev => [...prev, { ...item, id, timestamp }]);
|
|
984
|
+
return id;
|
|
985
|
+
}, []);
|
|
986
|
+
// Document preview expansion state (for collapsible document previews)
|
|
987
|
+
const [expandedDocuments, setExpandedDocuments] = useState(new Set());
|
|
988
|
+
const [documentContents, setDocumentContents] = useState(new Map());
|
|
989
|
+
const clearHistory = useCallback(() => {
|
|
990
|
+
setUiMessages([]);
|
|
991
|
+
// Note: This doesn't clear orchestrator history - that's persistent
|
|
992
|
+
}, []);
|
|
993
|
+
// Combined history: orchestrator messages + UI messages, sorted by timestamp
|
|
994
|
+
const history = useMemo(() => {
|
|
995
|
+
const orchestratorItems = orchestratorHistory.map(msg => ({
|
|
996
|
+
id: msg.uuid,
|
|
997
|
+
timestamp: new Date(msg.timestamp).getTime(),
|
|
998
|
+
type: 'orchestrator',
|
|
999
|
+
message: msg,
|
|
1000
|
+
}));
|
|
1001
|
+
return [...orchestratorItems, ...uiMessages].sort((a, b) => a.timestamp - b.timestamp);
|
|
1002
|
+
}, [orchestratorHistory, uiMessages]);
|
|
1003
|
+
// Extract user messages from history for input history navigation
|
|
1004
|
+
const userMessages = useMemo(() => {
|
|
1005
|
+
return history
|
|
1006
|
+
.filter((item) => item.type === MessageType.User)
|
|
1007
|
+
.map(item => {
|
|
1008
|
+
if ('userContent' in item && item.userContent) {
|
|
1009
|
+
return typeof item.userContent === 'string'
|
|
1010
|
+
? item.userContent
|
|
1011
|
+
: item.userContent.text || '';
|
|
1012
|
+
}
|
|
1013
|
+
return '';
|
|
1014
|
+
})
|
|
1015
|
+
.filter(text => text.length > 0);
|
|
1016
|
+
}, [history]);
|
|
1017
|
+
// YOLO mode toggle callback for approval handler
|
|
1018
|
+
const handleYoloToggle = useCallback(() => {
|
|
1019
|
+
setAutoApprove(prev => {
|
|
1020
|
+
const newValue = !prev;
|
|
1021
|
+
// Also update the orchestrator
|
|
1022
|
+
if (client) {
|
|
1023
|
+
if (newValue) {
|
|
1024
|
+
client.enableYoloMode();
|
|
1025
|
+
}
|
|
1026
|
+
else {
|
|
1027
|
+
client.disableYoloMode();
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
return newValue;
|
|
1031
|
+
});
|
|
1032
|
+
}, [client]);
|
|
1033
|
+
// React-compatible approval handler
|
|
1034
|
+
const { handler: approvalHandler, pendingApproval, approve, deny, approveAndEnableYolo, } = useReactApprovalHandler(handleYoloToggle);
|
|
1035
|
+
// Initialize orchestrator client
|
|
1036
|
+
useEffect(() => {
|
|
1037
|
+
const initClient = async () => {
|
|
1038
|
+
try {
|
|
1039
|
+
// Priority: CLI arg > persisted model > env var > fallback
|
|
1040
|
+
const persistedModel = loadPersistedModel();
|
|
1041
|
+
const effectiveModelId = modelId || persistedModel || process.env.DEFAULT_MODEL_ID || 'grok-code-fast-1';
|
|
1042
|
+
const orchestratorClient = new OrchestratorClient({
|
|
1043
|
+
mode: 'direct',
|
|
1044
|
+
defaultModelId: effectiveModelId,
|
|
1045
|
+
projectPath: projectPath || process.cwd(),
|
|
1046
|
+
debug,
|
|
1047
|
+
});
|
|
1048
|
+
await orchestratorClient.initialize();
|
|
1049
|
+
// Set our React-compatible approval handler
|
|
1050
|
+
orchestratorClient.setApprovalHandler(approvalHandler);
|
|
1051
|
+
// Set sub-agent event callback for parallel agent display
|
|
1052
|
+
orchestratorClient.setSubAgentEventCallback(handleSubAgentEvent);
|
|
1053
|
+
setClient(orchestratorClient);
|
|
1054
|
+
setInitialized(true);
|
|
1055
|
+
// Get actual model name and capabilities
|
|
1056
|
+
const model = orchestratorClient.getCurrentModel();
|
|
1057
|
+
if (model) {
|
|
1058
|
+
setCurrentModel(model.name || model.id);
|
|
1059
|
+
// Check if model supports reasoning
|
|
1060
|
+
setSupportsReasoning(model.reasoning?.supported === true);
|
|
1061
|
+
setReasoningToggleable(model.reasoning?.toggleable === true);
|
|
1062
|
+
}
|
|
1063
|
+
// Set initial YOLO mode if requested
|
|
1064
|
+
if (initialAutoApprove) {
|
|
1065
|
+
orchestratorClient.enableYoloMode();
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
catch (err) {
|
|
1069
|
+
setInitError(err instanceof Error ? err.message : 'Failed to initialize');
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1072
|
+
initClient();
|
|
1073
|
+
}, [modelId, projectPath, debug, approvalHandler, initialAutoApprove, handleSubAgentEvent]);
|
|
1074
|
+
// Debug message handler
|
|
1075
|
+
const handleDebugMessage = useCallback((message) => {
|
|
1076
|
+
if (debug) {
|
|
1077
|
+
console.log(`[Debug] ${message}`);
|
|
1078
|
+
}
|
|
1079
|
+
}, [debug]);
|
|
1080
|
+
// Parse slash command into parts
|
|
1081
|
+
const parseSlashCommand = useCallback((cmd) => {
|
|
1082
|
+
const withoutSlash = cmd.slice(1).trim();
|
|
1083
|
+
const parts = withoutSlash.split(/\s+/);
|
|
1084
|
+
const command = parts[0]?.toLowerCase() || '';
|
|
1085
|
+
const subcommand = parts[1]?.toLowerCase();
|
|
1086
|
+
const args = parts.slice(2);
|
|
1087
|
+
return { command, subcommand, args, raw: withoutSlash };
|
|
1088
|
+
}, []);
|
|
1089
|
+
// Slash command handler - expanded with all chalk CLI commands
|
|
1090
|
+
const handleSlashCommand = useCallback(async (cmd) => {
|
|
1091
|
+
const { command, subcommand, args, raw } = parseSlashCommand(cmd);
|
|
1092
|
+
switch (command) {
|
|
1093
|
+
case 'help':
|
|
1094
|
+
case '?':
|
|
1095
|
+
setShowHelp(true);
|
|
1096
|
+
return { handled: true };
|
|
1097
|
+
case 'clear':
|
|
1098
|
+
clearHistory();
|
|
1099
|
+
addItem({ type: MessageType.Info, infoContent: 'History cleared' }, Date.now());
|
|
1100
|
+
return { handled: true };
|
|
1101
|
+
case 'exit':
|
|
1102
|
+
case 'quit':
|
|
1103
|
+
case 'q':
|
|
1104
|
+
exit();
|
|
1105
|
+
return { handled: true };
|
|
1106
|
+
case 'yolo': {
|
|
1107
|
+
const isDisabling = args[0] === 'off';
|
|
1108
|
+
const isYoloActive = client?.isYoloModeActive() || false;
|
|
1109
|
+
if (isDisabling || isYoloActive) {
|
|
1110
|
+
client?.disableYoloMode();
|
|
1111
|
+
setAutoApprove(false);
|
|
1112
|
+
addItem({ type: MessageType.Info, infoContent: 'YOLO mode disabled. Interactive approval restored.' }, Date.now());
|
|
1113
|
+
}
|
|
1114
|
+
else {
|
|
1115
|
+
client?.enableYoloMode();
|
|
1116
|
+
setAutoApprove(true);
|
|
1117
|
+
addItem({ type: MessageType.Info, infoContent: ' YOLO MODE ACTIVATED - All tools auto-approved!' }, Date.now());
|
|
1118
|
+
}
|
|
1119
|
+
return { handled: true };
|
|
1120
|
+
}
|
|
1121
|
+
case 'thinking':
|
|
1122
|
+
setShowThinking(prev => !prev);
|
|
1123
|
+
addItem({
|
|
1124
|
+
type: MessageType.Info,
|
|
1125
|
+
infoContent: `Show thinking: ${!showThinking ? 'ON' : 'OFF'}`,
|
|
1126
|
+
}, Date.now());
|
|
1127
|
+
return { handled: true };
|
|
1128
|
+
case 'theme': {
|
|
1129
|
+
// /theme - show current theme
|
|
1130
|
+
// /theme list - list all themes
|
|
1131
|
+
// /theme set <name> - set theme
|
|
1132
|
+
// /theme picker - open interactive theme picker
|
|
1133
|
+
if (!subcommand) {
|
|
1134
|
+
const themeDef = getThemeDefinition(currentTheme);
|
|
1135
|
+
addItem({
|
|
1136
|
+
type: MessageType.Info,
|
|
1137
|
+
infoContent: `Current theme: ${themeDef?.name || currentTheme}\nTip: Use /theme picker for interactive selection`,
|
|
1138
|
+
}, Date.now());
|
|
1139
|
+
return { handled: true };
|
|
1140
|
+
}
|
|
1141
|
+
if (subcommand === 'picker' || subcommand === 'pick') {
|
|
1142
|
+
setShowThemePicker(true);
|
|
1143
|
+
return { handled: true };
|
|
1144
|
+
}
|
|
1145
|
+
if (subcommand === 'list') {
|
|
1146
|
+
const themes = getThemeNames();
|
|
1147
|
+
const themeList = themes.map(name => {
|
|
1148
|
+
const def = getThemeDefinition(name);
|
|
1149
|
+
const marker = name === currentTheme ? '●' : '○';
|
|
1150
|
+
return ` ${marker} ${name} - ${def?.name || name}`;
|
|
1151
|
+
}).join('\n');
|
|
1152
|
+
addItem({
|
|
1153
|
+
type: MessageType.Info,
|
|
1154
|
+
infoContent: `Available themes:\n${themeList}`,
|
|
1155
|
+
}, Date.now());
|
|
1156
|
+
return { handled: true };
|
|
1157
|
+
}
|
|
1158
|
+
if (subcommand === 'set') {
|
|
1159
|
+
const themeName = args[0];
|
|
1160
|
+
if (!themeName) {
|
|
1161
|
+
addItem({
|
|
1162
|
+
type: MessageType.Error,
|
|
1163
|
+
errorContent: 'Usage: /theme set <theme-name>',
|
|
1164
|
+
}, Date.now());
|
|
1165
|
+
return { handled: true };
|
|
1166
|
+
}
|
|
1167
|
+
if (setTheme(themeName)) {
|
|
1168
|
+
setCurrentTheme(themeName);
|
|
1169
|
+
const themeDef = getThemeDefinition(themeName);
|
|
1170
|
+
addItem({
|
|
1171
|
+
type: MessageType.Info,
|
|
1172
|
+
infoContent: `✓ Theme set to: ${themeDef?.name || themeName}`,
|
|
1173
|
+
}, Date.now());
|
|
1174
|
+
}
|
|
1175
|
+
else {
|
|
1176
|
+
const available = getThemeNames().join(', ');
|
|
1177
|
+
addItem({
|
|
1178
|
+
type: MessageType.Error,
|
|
1179
|
+
errorContent: `Unknown theme: ${themeName}\nAvailable: ${available}`,
|
|
1180
|
+
}, Date.now());
|
|
1181
|
+
}
|
|
1182
|
+
return { handled: true };
|
|
1183
|
+
}
|
|
1184
|
+
addItem({
|
|
1185
|
+
type: MessageType.Error,
|
|
1186
|
+
errorContent: 'Usage: /theme [list | set <name> | picker]',
|
|
1187
|
+
}, Date.now());
|
|
1188
|
+
return { handled: true };
|
|
1189
|
+
}
|
|
1190
|
+
case 'm':
|
|
1191
|
+
case 'model': {
|
|
1192
|
+
// /model picker - open interactive model picker
|
|
1193
|
+
if (subcommand === 'picker' || subcommand === 'pick') {
|
|
1194
|
+
if (!client) {
|
|
1195
|
+
addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
|
|
1196
|
+
return { handled: true };
|
|
1197
|
+
}
|
|
1198
|
+
// Load models and open picker
|
|
1199
|
+
setModelsLoading(true);
|
|
1200
|
+
setShowModelPicker(true);
|
|
1201
|
+
try {
|
|
1202
|
+
const models = await client.listModels();
|
|
1203
|
+
const modelDisplayInfos = models.map((m) => ({
|
|
1204
|
+
id: m.id,
|
|
1205
|
+
displayName: m.displayName || m.id,
|
|
1206
|
+
provider: m.owned_by || m.provider || 'Unknown',
|
|
1207
|
+
contextWindow: m.contextWindow,
|
|
1208
|
+
supportsReasoning: m.reasoning?.supported === true,
|
|
1209
|
+
reasoningToggleable: m.reasoning?.toggleable === true,
|
|
1210
|
+
inputCost: m.inputCostPer1M,
|
|
1211
|
+
outputCost: m.outputCostPer1M,
|
|
1212
|
+
}));
|
|
1213
|
+
setAvailableModels(modelDisplayInfos);
|
|
1214
|
+
}
|
|
1215
|
+
catch (error) {
|
|
1216
|
+
addItem({ type: MessageType.Error, errorContent: `Error loading models: ${error.message}` }, Date.now());
|
|
1217
|
+
setShowModelPicker(false);
|
|
1218
|
+
}
|
|
1219
|
+
finally {
|
|
1220
|
+
setModelsLoading(false);
|
|
1221
|
+
}
|
|
1222
|
+
return { handled: true };
|
|
1223
|
+
}
|
|
1224
|
+
// /model alone shows current model
|
|
1225
|
+
addItem({
|
|
1226
|
+
type: MessageType.Info,
|
|
1227
|
+
infoContent: `Current model: ${currentModel}\nTip: Use /model picker for interactive selection`,
|
|
1228
|
+
}, Date.now());
|
|
1229
|
+
return { handled: true };
|
|
1230
|
+
}
|
|
1231
|
+
case 'session':
|
|
1232
|
+
if (subcommand === 'list') {
|
|
1233
|
+
// /session list
|
|
1234
|
+
if (!client) {
|
|
1235
|
+
addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
|
|
1236
|
+
return { handled: true };
|
|
1237
|
+
}
|
|
1238
|
+
try {
|
|
1239
|
+
const result = await client.listSessions();
|
|
1240
|
+
const sessions = result.sessions;
|
|
1241
|
+
if (sessions && sessions.length > 0) {
|
|
1242
|
+
const sessionList = sessions.slice(0, 10).map((s) => ` ${s.sessionId?.slice(0, 8) || s.id?.slice(0, 8)} - ${s.messageCount || 0} messages`).join('\n');
|
|
1243
|
+
addItem({
|
|
1244
|
+
type: MessageType.Info,
|
|
1245
|
+
infoContent: `Sessions:\n${sessionList}${sessions.length > 10 ? `\n ... and ${sessions.length - 10} more` : ''}`,
|
|
1246
|
+
}, Date.now());
|
|
1247
|
+
}
|
|
1248
|
+
else {
|
|
1249
|
+
addItem({ type: MessageType.Info, infoContent: 'No sessions found' }, Date.now());
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
catch (error) {
|
|
1253
|
+
addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
|
|
1254
|
+
}
|
|
1255
|
+
return { handled: true };
|
|
1256
|
+
}
|
|
1257
|
+
if (subcommand === 'checkpoint') {
|
|
1258
|
+
// /session checkpoint [name]
|
|
1259
|
+
const checkpointName = args.join(' ') || `Checkpoint ${new Date().toLocaleString()}`;
|
|
1260
|
+
if (!client) {
|
|
1261
|
+
addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
|
|
1262
|
+
return { handled: true };
|
|
1263
|
+
}
|
|
1264
|
+
try {
|
|
1265
|
+
const checkpoint = await client.createCheckpoint(checkpointName);
|
|
1266
|
+
addItem({
|
|
1267
|
+
type: MessageType.Info,
|
|
1268
|
+
infoContent: `✓ Checkpoint created: ${checkpoint.id?.slice(0, 8) || 'unknown'} - "${checkpointName}"`,
|
|
1269
|
+
}, Date.now());
|
|
1270
|
+
}
|
|
1271
|
+
catch (error) {
|
|
1272
|
+
addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
|
|
1273
|
+
}
|
|
1274
|
+
return { handled: true };
|
|
1275
|
+
}
|
|
1276
|
+
addItem({ type: MessageType.Info, infoContent: 'Available: /session list, /session checkpoint [name]' }, Date.now());
|
|
1277
|
+
return { handled: true };
|
|
1278
|
+
case 'cache':
|
|
1279
|
+
if (subcommand === 'metrics') {
|
|
1280
|
+
// /cache metrics
|
|
1281
|
+
if (!client) {
|
|
1282
|
+
addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
|
|
1283
|
+
return { handled: true };
|
|
1284
|
+
}
|
|
1285
|
+
try {
|
|
1286
|
+
const result = await client.getCacheMetrics();
|
|
1287
|
+
if (result?.metrics) {
|
|
1288
|
+
const m = result.metrics;
|
|
1289
|
+
const hitRate = m.overallCacheHitRate ? `${(m.overallCacheHitRate * 100).toFixed(1)}%` : '0%';
|
|
1290
|
+
const costSavings = m.overallCostSavingsRatio ? `${(m.overallCostSavingsRatio * 100).toFixed(1)}%` : '0%';
|
|
1291
|
+
const lines = [
|
|
1292
|
+
`Cache Metrics:`,
|
|
1293
|
+
` Total Requests: ${m.requestCount || 0}`,
|
|
1294
|
+
` Requests with Cache Hits: ${m.requestsWithCacheHits || 0}`,
|
|
1295
|
+
'',
|
|
1296
|
+
` Total Input Tokens: ${(m.totalInputTokens || 0).toLocaleString()}`,
|
|
1297
|
+
` Cache Creation: ${(m.totalCacheCreationTokens || 0).toLocaleString()}`,
|
|
1298
|
+
` Cache Reads: ${(m.totalCacheReadTokens || 0).toLocaleString()}`,
|
|
1299
|
+
` Uncached: ${(m.totalUncachedInputTokens || 0).toLocaleString()}`,
|
|
1300
|
+
'',
|
|
1301
|
+
` Cache Hit Rate: ${hitRate}`,
|
|
1302
|
+
` Est. Cost Savings: ${costSavings}`
|
|
1303
|
+
];
|
|
1304
|
+
// Show provider breakdown if available
|
|
1305
|
+
if (Object.keys(m.byProvider || {}).length > 0) {
|
|
1306
|
+
lines.push('');
|
|
1307
|
+
lines.push(' By Provider:');
|
|
1308
|
+
for (const [provider, pm] of Object.entries(m.byProvider)) {
|
|
1309
|
+
const providerHitRate = pm.cacheHitRate ? `${(pm.cacheHitRate * 100).toFixed(1)}%` : '0%';
|
|
1310
|
+
lines.push(` ${provider}: ${pm.cacheReadTokens.toLocaleString()} cached (${providerHitRate})`);
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
addItem({ type: MessageType.Info, infoContent: lines.join('\n') }, Date.now());
|
|
1314
|
+
}
|
|
1315
|
+
else {
|
|
1316
|
+
addItem({
|
|
1317
|
+
type: MessageType.Info,
|
|
1318
|
+
infoContent: 'No cache metrics available\n[i] Make some API requests with caching enabled'
|
|
1319
|
+
}, Date.now());
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
catch (error) {
|
|
1323
|
+
addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
|
|
1324
|
+
}
|
|
1325
|
+
return { handled: true };
|
|
1326
|
+
}
|
|
1327
|
+
if (subcommand === 'report') {
|
|
1328
|
+
// /cache report
|
|
1329
|
+
if (!client) {
|
|
1330
|
+
addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
|
|
1331
|
+
return { handled: true };
|
|
1332
|
+
}
|
|
1333
|
+
try {
|
|
1334
|
+
const report = await client.getCacheReport();
|
|
1335
|
+
addItem({ type: MessageType.Info, infoContent: report || 'No cache report available' }, Date.now());
|
|
1336
|
+
}
|
|
1337
|
+
catch (error) {
|
|
1338
|
+
addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
|
|
1339
|
+
}
|
|
1340
|
+
return { handled: true };
|
|
1341
|
+
}
|
|
1342
|
+
addItem({ type: MessageType.Info, infoContent: 'Available: /cache metrics, /cache report' }, Date.now());
|
|
1343
|
+
return { handled: true };
|
|
1344
|
+
case 'mcp':
|
|
1345
|
+
if (subcommand === 'list') {
|
|
1346
|
+
// /mcp list
|
|
1347
|
+
if (!client) {
|
|
1348
|
+
addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
|
|
1349
|
+
return { handled: true };
|
|
1350
|
+
}
|
|
1351
|
+
try {
|
|
1352
|
+
const result = await client.listMcpServers();
|
|
1353
|
+
if (!result.enabled) {
|
|
1354
|
+
addItem({ type: MessageType.Info, infoContent: 'MCP is not enabled' }, Date.now());
|
|
1355
|
+
return { handled: true };
|
|
1356
|
+
}
|
|
1357
|
+
const servers = result.servers;
|
|
1358
|
+
if (servers && servers.length > 0) {
|
|
1359
|
+
const serverList = servers.map((s) => ` ${s.name} [${s.enabled ? 'enabled' : 'disabled'}]${s.description ? ` - ${s.description}` : ''}`).join('\n');
|
|
1360
|
+
addItem({ type: MessageType.Info, infoContent: `MCP Servers:\n${serverList}` }, Date.now());
|
|
1361
|
+
}
|
|
1362
|
+
else {
|
|
1363
|
+
addItem({ type: MessageType.Info, infoContent: 'No MCP servers configured' }, Date.now());
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
catch (error) {
|
|
1367
|
+
addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
|
|
1368
|
+
}
|
|
1369
|
+
return { handled: true };
|
|
1370
|
+
}
|
|
1371
|
+
if (subcommand === 'enable') {
|
|
1372
|
+
const serverName = args[0];
|
|
1373
|
+
if (!serverName) {
|
|
1374
|
+
addItem({ type: MessageType.Error, errorContent: 'Usage: /mcp enable <server-name>' }, Date.now());
|
|
1375
|
+
return { handled: true };
|
|
1376
|
+
}
|
|
1377
|
+
if (!client) {
|
|
1378
|
+
addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
|
|
1379
|
+
return { handled: true };
|
|
1380
|
+
}
|
|
1381
|
+
try {
|
|
1382
|
+
await client.enableMCPServer(serverName);
|
|
1383
|
+
addItem({ type: MessageType.Info, infoContent: `✓ Enabled MCP server: ${serverName}` }, Date.now());
|
|
1384
|
+
}
|
|
1385
|
+
catch (error) {
|
|
1386
|
+
addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
|
|
1387
|
+
}
|
|
1388
|
+
return { handled: true };
|
|
1389
|
+
}
|
|
1390
|
+
if (subcommand === 'disable') {
|
|
1391
|
+
const serverName = args[0];
|
|
1392
|
+
if (!serverName) {
|
|
1393
|
+
addItem({ type: MessageType.Error, errorContent: 'Usage: /mcp disable <server-name>' }, Date.now());
|
|
1394
|
+
return { handled: true };
|
|
1395
|
+
}
|
|
1396
|
+
if (!client) {
|
|
1397
|
+
addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
|
|
1398
|
+
return { handled: true };
|
|
1399
|
+
}
|
|
1400
|
+
try {
|
|
1401
|
+
await client.disableMCPServer(serverName);
|
|
1402
|
+
addItem({ type: MessageType.Info, infoContent: `✓ Disabled MCP server: ${serverName}` }, Date.now());
|
|
1403
|
+
}
|
|
1404
|
+
catch (error) {
|
|
1405
|
+
addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
|
|
1406
|
+
}
|
|
1407
|
+
return { handled: true };
|
|
1408
|
+
}
|
|
1409
|
+
addItem({ type: MessageType.Info, infoContent: 'Available: /mcp list, /mcp enable <name>, /mcp disable <name>' }, Date.now());
|
|
1410
|
+
return { handled: true };
|
|
1411
|
+
case 'tools':
|
|
1412
|
+
if (subcommand === 'list') {
|
|
1413
|
+
// /tools list [--grouped]
|
|
1414
|
+
const grouped = args.includes('--grouped');
|
|
1415
|
+
if (!client) {
|
|
1416
|
+
addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
|
|
1417
|
+
return { handled: true };
|
|
1418
|
+
}
|
|
1419
|
+
try {
|
|
1420
|
+
const result = await client.listTools(grouped);
|
|
1421
|
+
if (grouped && result.grouped) {
|
|
1422
|
+
let output = 'Available Tools:\n';
|
|
1423
|
+
for (const [category, tools] of Object.entries(result.grouped)) {
|
|
1424
|
+
output += `\n ${category}:\n`;
|
|
1425
|
+
for (const tool of tools) {
|
|
1426
|
+
output += ` ${tool.name}${tool.description ? ` - ${tool.description.slice(0, 50)}...` : ''}\n`;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
addItem({ type: MessageType.Info, infoContent: output }, Date.now());
|
|
1430
|
+
}
|
|
1431
|
+
else if (result.tools) {
|
|
1432
|
+
const toolList = result.tools.slice(0, 20).map((t) => ` ${t.name}${t.description ? ` - ${t.description.slice(0, 40)}...` : ''}`).join('\n');
|
|
1433
|
+
addItem({
|
|
1434
|
+
type: MessageType.Info,
|
|
1435
|
+
infoContent: `Available Tools (${result.totalCount}):\n${toolList}${result.tools.length > 20 ? '\n ...' : ''}`,
|
|
1436
|
+
}, Date.now());
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
catch (error) {
|
|
1440
|
+
addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
|
|
1441
|
+
}
|
|
1442
|
+
return { handled: true };
|
|
1443
|
+
}
|
|
1444
|
+
if (subcommand === 'info') {
|
|
1445
|
+
const toolName = args[0];
|
|
1446
|
+
if (!toolName) {
|
|
1447
|
+
addItem({ type: MessageType.Error, errorContent: 'Usage: /tools info <tool-name>' }, Date.now());
|
|
1448
|
+
return { handled: true };
|
|
1449
|
+
}
|
|
1450
|
+
if (!client) {
|
|
1451
|
+
addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
|
|
1452
|
+
return { handled: true };
|
|
1453
|
+
}
|
|
1454
|
+
try {
|
|
1455
|
+
const toolInfo = await client.getToolInfo(toolName);
|
|
1456
|
+
if (toolInfo) {
|
|
1457
|
+
const info = [
|
|
1458
|
+
`Tool: ${toolInfo.name}`,
|
|
1459
|
+
toolInfo.description ? `Description: ${toolInfo.description}` : '',
|
|
1460
|
+
toolInfo.category ? `Category: ${toolInfo.category}` : '',
|
|
1461
|
+
].filter(Boolean).join('\n ');
|
|
1462
|
+
addItem({ type: MessageType.Info, infoContent: info }, Date.now());
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
catch (error) {
|
|
1466
|
+
addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
|
|
1467
|
+
}
|
|
1468
|
+
return { handled: true };
|
|
1469
|
+
}
|
|
1470
|
+
addItem({ type: MessageType.Info, infoContent: 'Available: /tools list [--grouped], /tools info <name>' }, Date.now());
|
|
1471
|
+
return { handled: true };
|
|
1472
|
+
case 'init':
|
|
1473
|
+
// /init - Scan project, then let the model write CORTEX.md
|
|
1474
|
+
if (!client) {
|
|
1475
|
+
addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
|
|
1476
|
+
return { handled: true };
|
|
1477
|
+
}
|
|
1478
|
+
try {
|
|
1479
|
+
addItem({ type: MessageType.Info, infoContent: '[INIT] Scanning project...' }, Date.now());
|
|
1480
|
+
const { InitCortexContext } = await import('@nexus-cortex/core');
|
|
1481
|
+
const isGlobal = args.includes('--global');
|
|
1482
|
+
const depthIndex = args.indexOf('--depth');
|
|
1483
|
+
const depthArg = depthIndex >= 0 ? args[depthIndex + 1] : undefined;
|
|
1484
|
+
const scan = await InitCortexContext.scan(projectPath || process.cwd(), {
|
|
1485
|
+
scope: isGlobal ? 'global' : 'auto',
|
|
1486
|
+
max_depth: depthArg ? parseInt(depthArg, 10) : undefined,
|
|
1487
|
+
});
|
|
1488
|
+
const prompt = InitCortexContext.formatScanAsPrompt(scan);
|
|
1489
|
+
addItem({ type: MessageType.Info, infoContent: `[INIT] Scan complete. Generating CORTEX.md...` }, Date.now());
|
|
1490
|
+
return { type: 'submit_prompt', content: prompt };
|
|
1491
|
+
}
|
|
1492
|
+
catch (error) {
|
|
1493
|
+
addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
|
|
1494
|
+
}
|
|
1495
|
+
return { handled: true };
|
|
1496
|
+
case 'continue':
|
|
1497
|
+
// /continue - Interactive session picker
|
|
1498
|
+
if (!client) {
|
|
1499
|
+
addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
|
|
1500
|
+
return { handled: true };
|
|
1501
|
+
}
|
|
1502
|
+
try {
|
|
1503
|
+
setSessionsLoading(true);
|
|
1504
|
+
setShowSessionPicker(true);
|
|
1505
|
+
const { resolveContext, JSONLHistoryStore } = await import('@nexus-cortex/core');
|
|
1506
|
+
const context = resolveContext({ cwd: projectPath || process.cwd() });
|
|
1507
|
+
const historyStore = new JSONLHistoryStore({ baseDir: context.sessionsDir });
|
|
1508
|
+
const sessions = await historyStore.listSessions();
|
|
1509
|
+
// Sort by most recent
|
|
1510
|
+
sessions.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
1511
|
+
// Fetch first user message for each session (for preview)
|
|
1512
|
+
const sessionDisplayInfos = await Promise.all(sessions.slice(0, 50).map(async (s) => {
|
|
1513
|
+
let firstUserMessage;
|
|
1514
|
+
try {
|
|
1515
|
+
const messages = await historyStore.loadSession(s.sessionId);
|
|
1516
|
+
// Find first user message - messages have type: 'user'|'assistant'|etc and message object
|
|
1517
|
+
const firstUser = messages.find((m) => m.type === 'user');
|
|
1518
|
+
if (firstUser && firstUser.message) {
|
|
1519
|
+
const msg = firstUser.message;
|
|
1520
|
+
if (typeof msg.content === 'string') {
|
|
1521
|
+
firstUserMessage = msg.content.replace(/\n/g, ' ').trim();
|
|
1522
|
+
}
|
|
1523
|
+
else if (Array.isArray(msg.content)) {
|
|
1524
|
+
const textPart = msg.content.find((p) => p.type === 'text');
|
|
1525
|
+
if (textPart && textPart.text) {
|
|
1526
|
+
firstUserMessage = textPart.text.replace(/\n/g, ' ').trim();
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
catch {
|
|
1532
|
+
// Ignore errors loading individual sessions
|
|
1533
|
+
}
|
|
1534
|
+
return {
|
|
1535
|
+
sessionId: s.sessionId,
|
|
1536
|
+
shortId: s.sessionId.slice(0, 8),
|
|
1537
|
+
lastModified: s.lastModified,
|
|
1538
|
+
messageCount: s.messageCount,
|
|
1539
|
+
model: s.metadata?.currentModel,
|
|
1540
|
+
title: s.metadata?.title,
|
|
1541
|
+
firstUserMessage,
|
|
1542
|
+
age: formatSessionAge(s.lastModified),
|
|
1543
|
+
};
|
|
1544
|
+
}));
|
|
1545
|
+
setAvailableSessions(sessionDisplayInfos);
|
|
1546
|
+
setSessionsLoading(false);
|
|
1547
|
+
// Store sessions for /resume command (backwards compatibility)
|
|
1548
|
+
client._cachedSessions = sessions.slice(0, 50);
|
|
1549
|
+
}
|
|
1550
|
+
catch (error) {
|
|
1551
|
+
setShowSessionPicker(false);
|
|
1552
|
+
setSessionsLoading(false);
|
|
1553
|
+
addItem({ type: MessageType.Error, errorContent: `Error loading sessions: ${error.message}` }, Date.now());
|
|
1554
|
+
}
|
|
1555
|
+
return { handled: true };
|
|
1556
|
+
case 'resume': {
|
|
1557
|
+
// /resume <number|session-id>
|
|
1558
|
+
const selector = raw.split(/\s+/)[1];
|
|
1559
|
+
if (!selector) {
|
|
1560
|
+
addItem({ type: MessageType.Error, errorContent: 'Usage: /resume <number> or /resume <session-id>' }, Date.now());
|
|
1561
|
+
return { handled: true };
|
|
1562
|
+
}
|
|
1563
|
+
if (!client) {
|
|
1564
|
+
addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
|
|
1565
|
+
return { handled: true };
|
|
1566
|
+
}
|
|
1567
|
+
try {
|
|
1568
|
+
let sessionId;
|
|
1569
|
+
const cachedSessions = client._cachedSessions;
|
|
1570
|
+
// Check if selector is a number (index) or session ID
|
|
1571
|
+
const num = parseInt(selector, 10);
|
|
1572
|
+
if (!isNaN(num) && cachedSessions && num >= 1 && num <= cachedSessions.length) {
|
|
1573
|
+
sessionId = cachedSessions[num - 1].sessionId;
|
|
1574
|
+
}
|
|
1575
|
+
else {
|
|
1576
|
+
// Treat as session ID (or prefix)
|
|
1577
|
+
sessionId = selector;
|
|
1578
|
+
}
|
|
1579
|
+
addItem({ type: MessageType.Info, infoContent: `Resuming session ${sessionId.slice(0, 8)}...` }, Date.now());
|
|
1580
|
+
await client.resumeSession(sessionId);
|
|
1581
|
+
// Refresh history to display the loaded session's messages
|
|
1582
|
+
refreshOrchestratorHistory();
|
|
1583
|
+
addItem({ type: MessageType.Info, infoContent: `✓ Session resumed. Continue your conversation.` }, Date.now());
|
|
1584
|
+
}
|
|
1585
|
+
catch (error) {
|
|
1586
|
+
addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
|
|
1587
|
+
}
|
|
1588
|
+
return { handled: true };
|
|
1589
|
+
}
|
|
1590
|
+
case 'debug': {
|
|
1591
|
+
const newDebugState = !(client?.isDebugActive() ?? process.env.DEBUG === 'true');
|
|
1592
|
+
if (client) {
|
|
1593
|
+
client.setDebug(newDebugState);
|
|
1594
|
+
}
|
|
1595
|
+
else {
|
|
1596
|
+
process.env.DEBUG = newDebugState ? 'true' : 'false';
|
|
1597
|
+
}
|
|
1598
|
+
addItem({
|
|
1599
|
+
type: MessageType.Info,
|
|
1600
|
+
infoContent: `Debug mode: ${newDebugState ? 'ON' : 'OFF'}`,
|
|
1601
|
+
}, Date.now());
|
|
1602
|
+
return { handled: true };
|
|
1603
|
+
}
|
|
1604
|
+
case 'config': {
|
|
1605
|
+
// /config - show summary
|
|
1606
|
+
// /config list - list all config keys
|
|
1607
|
+
// /config get <key> - get specific value
|
|
1608
|
+
// /config set <key> <value> - set value
|
|
1609
|
+
if (!client) {
|
|
1610
|
+
addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
|
|
1611
|
+
return { handled: true };
|
|
1612
|
+
}
|
|
1613
|
+
if (!subcommand) {
|
|
1614
|
+
setShowConfigMenu(true);
|
|
1615
|
+
return { handled: true };
|
|
1616
|
+
}
|
|
1617
|
+
if (subcommand === 'list') {
|
|
1618
|
+
try {
|
|
1619
|
+
const keys = await client.listConfigKeys();
|
|
1620
|
+
const keyList = keys.map(k => ` ${k}`).join('\n');
|
|
1621
|
+
addItem({
|
|
1622
|
+
type: MessageType.Info,
|
|
1623
|
+
infoContent: `Configuration Keys:\n${keyList}`,
|
|
1624
|
+
}, Date.now());
|
|
1625
|
+
}
|
|
1626
|
+
catch (error) {
|
|
1627
|
+
addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
|
|
1628
|
+
}
|
|
1629
|
+
return { handled: true };
|
|
1630
|
+
}
|
|
1631
|
+
if (subcommand === 'get') {
|
|
1632
|
+
const key = args[0];
|
|
1633
|
+
if (!key) {
|
|
1634
|
+
addItem({ type: MessageType.Error, errorContent: 'Usage: /config get <key>' }, Date.now());
|
|
1635
|
+
return { handled: true };
|
|
1636
|
+
}
|
|
1637
|
+
try {
|
|
1638
|
+
const value = await client.getConfig(key);
|
|
1639
|
+
// Mask API keys
|
|
1640
|
+
const displayValue = key.includes('API_KEY') && value ? '***configured***' : value;
|
|
1641
|
+
addItem({
|
|
1642
|
+
type: MessageType.Info,
|
|
1643
|
+
infoContent: `${key} = ${displayValue}`,
|
|
1644
|
+
}, Date.now());
|
|
1645
|
+
}
|
|
1646
|
+
catch (error) {
|
|
1647
|
+
addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
|
|
1648
|
+
}
|
|
1649
|
+
return { handled: true };
|
|
1650
|
+
}
|
|
1651
|
+
if (subcommand === 'set') {
|
|
1652
|
+
const key = args[0];
|
|
1653
|
+
const value = args.slice(1).join(' ');
|
|
1654
|
+
if (!key || !value) {
|
|
1655
|
+
setShowConfigMenu(true);
|
|
1656
|
+
return { handled: true };
|
|
1657
|
+
}
|
|
1658
|
+
try {
|
|
1659
|
+
await client.setConfig(key, value);
|
|
1660
|
+
const { getRuntimeConfigEntry, isLiveToggleable } = await import('@nexus-cortex/core');
|
|
1661
|
+
const entry = getRuntimeConfigEntry(key);
|
|
1662
|
+
if (entry?.tier === 'config' && entry.mapper) {
|
|
1663
|
+
client.updateRuntimeConfig(entry.mapper(value));
|
|
1664
|
+
}
|
|
1665
|
+
addItem({
|
|
1666
|
+
type: MessageType.Info,
|
|
1667
|
+
infoContent: isLiveToggleable(key)
|
|
1668
|
+
? `[OK] ${key} updated (live)`
|
|
1669
|
+
: `[OK] ${key} updated (restart required)`,
|
|
1670
|
+
}, Date.now());
|
|
1671
|
+
}
|
|
1672
|
+
catch (error) {
|
|
1673
|
+
addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
|
|
1674
|
+
}
|
|
1675
|
+
return { handled: true };
|
|
1676
|
+
}
|
|
1677
|
+
if (subcommand === 'reset') {
|
|
1678
|
+
try {
|
|
1679
|
+
const { configReset } = await import('@nexus-cortex/cli/dist/commands/config/reset.js');
|
|
1680
|
+
await configReset();
|
|
1681
|
+
addItem({
|
|
1682
|
+
type: MessageType.Info,
|
|
1683
|
+
infoContent: '[OK] Configuration reset to benchmark-proven optimal defaults (API keys preserved). Restart recommended.',
|
|
1684
|
+
}, Date.now());
|
|
1685
|
+
}
|
|
1686
|
+
catch (error) {
|
|
1687
|
+
addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
|
|
1688
|
+
}
|
|
1689
|
+
return { handled: true };
|
|
1690
|
+
}
|
|
1691
|
+
addItem({
|
|
1692
|
+
type: MessageType.Info,
|
|
1693
|
+
infoContent: 'Available: /config, /config list, /config get <key>, /config set <key> <value>, /config reset',
|
|
1694
|
+
}, Date.now());
|
|
1695
|
+
return { handled: true };
|
|
1696
|
+
}
|
|
1697
|
+
case 'system-message':
|
|
1698
|
+
case 'sysmsg': {
|
|
1699
|
+
// /system-message - open interactive manager (default)
|
|
1700
|
+
// /system-message list - quick list of system messages
|
|
1701
|
+
// /system-message view <filename> - view content
|
|
1702
|
+
if (!client) {
|
|
1703
|
+
addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
|
|
1704
|
+
return { handled: true };
|
|
1705
|
+
}
|
|
1706
|
+
// Interactive manager mode (default or 'manage' subcommand)
|
|
1707
|
+
if (!subcommand || subcommand === 'manage') {
|
|
1708
|
+
try {
|
|
1709
|
+
// Get context-aware paths using ContextResolver
|
|
1710
|
+
const { resolveContext } = await import('@nexus-cortex/core/utils/ContextResolver.js');
|
|
1711
|
+
const context = resolveContext({ cwd: projectPath, debug });
|
|
1712
|
+
// Builtin directory: relative to package location
|
|
1713
|
+
const { fileURLToPath } = await import('url');
|
|
1714
|
+
const { dirname, join } = await import('path');
|
|
1715
|
+
// packages/cli/dist/ink-ui/CortexApp.js -> packages/core/dist/system-messages
|
|
1716
|
+
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
1717
|
+
const cliDistDir = dirname(currentDir); // packages/cli/dist
|
|
1718
|
+
const packagesDir = dirname(dirname(cliDistDir)); // packages/
|
|
1719
|
+
const builtinDir = join(packagesDir, 'core', 'dist', 'system-messages');
|
|
1720
|
+
// Initialize store
|
|
1721
|
+
const store = new SystemMessageStore({
|
|
1722
|
+
builtinDir,
|
|
1723
|
+
runtimeDir: context.systemMessagesDir,
|
|
1724
|
+
enableWatching: false, // Don't watch in dialog mode
|
|
1725
|
+
debug: debug || false,
|
|
1726
|
+
});
|
|
1727
|
+
await store.initialize();
|
|
1728
|
+
setSystemMessageStore(store);
|
|
1729
|
+
setShowSystemMessageManager(true);
|
|
1730
|
+
addItem({
|
|
1731
|
+
type: MessageType.Info,
|
|
1732
|
+
infoContent: ' Opening System Message Manager...',
|
|
1733
|
+
}, Date.now());
|
|
1734
|
+
}
|
|
1735
|
+
catch (error) {
|
|
1736
|
+
addItem({ type: MessageType.Error, errorContent: `Error opening manager: ${error.message}` }, Date.now());
|
|
1737
|
+
}
|
|
1738
|
+
return { handled: true };
|
|
1739
|
+
}
|
|
1740
|
+
if (subcommand === 'list') {
|
|
1741
|
+
try {
|
|
1742
|
+
const messages = await client.listSystemMessages();
|
|
1743
|
+
if (messages.length === 0) {
|
|
1744
|
+
addItem({
|
|
1745
|
+
type: MessageType.Info,
|
|
1746
|
+
infoContent: 'No system messages found.\nCreate files in .cortex/system-messages/ (e.g., 01-base.md)\n\nUse /system-message to open interactive manager',
|
|
1747
|
+
}, Date.now());
|
|
1748
|
+
}
|
|
1749
|
+
else {
|
|
1750
|
+
const msgList = messages.map(m => ` [${String(m.priority).padStart(2, '0')}] ${m.filename}`).join('\n');
|
|
1751
|
+
addItem({
|
|
1752
|
+
type: MessageType.Info,
|
|
1753
|
+
infoContent: `System Messages:\n${msgList}\n\nUse /system-message to manage interactively`,
|
|
1754
|
+
}, Date.now());
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
catch (error) {
|
|
1758
|
+
addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
|
|
1759
|
+
}
|
|
1760
|
+
return { handled: true };
|
|
1761
|
+
}
|
|
1762
|
+
if (subcommand === 'view') {
|
|
1763
|
+
const filename = args[0];
|
|
1764
|
+
if (!filename) {
|
|
1765
|
+
addItem({ type: MessageType.Error, errorContent: 'Usage: /system-message view <filename>' }, Date.now());
|
|
1766
|
+
return { handled: true };
|
|
1767
|
+
}
|
|
1768
|
+
try {
|
|
1769
|
+
const content = await client.getSystemMessageContent(filename);
|
|
1770
|
+
// Truncate if too long
|
|
1771
|
+
const maxLength = 2000;
|
|
1772
|
+
const displayContent = content.length > maxLength
|
|
1773
|
+
? content.slice(0, maxLength) + '\n... (truncated)'
|
|
1774
|
+
: content;
|
|
1775
|
+
addItem({
|
|
1776
|
+
type: MessageType.Info,
|
|
1777
|
+
infoContent: `─── ${filename} ───\n${displayContent}`,
|
|
1778
|
+
}, Date.now());
|
|
1779
|
+
}
|
|
1780
|
+
catch (error) {
|
|
1781
|
+
addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
|
|
1782
|
+
}
|
|
1783
|
+
return { handled: true };
|
|
1784
|
+
}
|
|
1785
|
+
addItem({
|
|
1786
|
+
type: MessageType.Info,
|
|
1787
|
+
infoContent: 'Available:\n /system-message - Interactive manager\n /system-message list - Quick list\n /system-message view <filename>',
|
|
1788
|
+
}, Date.now());
|
|
1789
|
+
return { handled: true };
|
|
1790
|
+
}
|
|
1791
|
+
case 'mentorship': {
|
|
1792
|
+
// Direct mentorship commands
|
|
1793
|
+
// Find installation root - prefer env var, then use module location
|
|
1794
|
+
const getInstallationRoot = () => {
|
|
1795
|
+
const root = process.env.CORTEX_ROOT;
|
|
1796
|
+
if (root)
|
|
1797
|
+
return root;
|
|
1798
|
+
return CLI_INSTALLATION_ROOT;
|
|
1799
|
+
};
|
|
1800
|
+
const mentorshipSvc = new MentorshipConfigService(getInstallationRoot());
|
|
1801
|
+
// /mentorship or /mentorship status - show status immediately
|
|
1802
|
+
if (!subcommand || subcommand === 'status') {
|
|
1803
|
+
const summary = mentorshipSvc.getSummary();
|
|
1804
|
+
const statusIcon = summary.status === 'enabled' ? '[ON]' : '[OFF]';
|
|
1805
|
+
addItem({
|
|
1806
|
+
type: MessageType.Info,
|
|
1807
|
+
infoContent: [
|
|
1808
|
+
`Mentorship: ${statusIcon} ${summary.status.toUpperCase()}`,
|
|
1809
|
+
`Helper: ${summary.helperModel}`,
|
|
1810
|
+
`Triggers: ${summary.triggers.join(', ')}`,
|
|
1811
|
+
'',
|
|
1812
|
+
'/mentorship enable|disable|config',
|
|
1813
|
+
].join('\n'),
|
|
1814
|
+
}, Date.now());
|
|
1815
|
+
return { handled: true };
|
|
1816
|
+
}
|
|
1817
|
+
if (subcommand === 'enable') {
|
|
1818
|
+
await mentorshipSvc.quickEnable();
|
|
1819
|
+
client?.updateRuntimeConfig({
|
|
1820
|
+
reactiveMentorship: {
|
|
1821
|
+
enabled: true,
|
|
1822
|
+
triggerOnError: true,
|
|
1823
|
+
errorSeverityThreshold: 'medium',
|
|
1824
|
+
enableKeywords: false,
|
|
1825
|
+
patternDetection: true,
|
|
1826
|
+
},
|
|
1827
|
+
});
|
|
1828
|
+
addItem({
|
|
1829
|
+
type: MessageType.Info,
|
|
1830
|
+
infoContent: '[OK] Mentorship enabled',
|
|
1831
|
+
}, Date.now());
|
|
1832
|
+
return { handled: true };
|
|
1833
|
+
}
|
|
1834
|
+
if (subcommand === 'disable') {
|
|
1835
|
+
await mentorshipSvc.quickDisable();
|
|
1836
|
+
client?.updateRuntimeConfig({
|
|
1837
|
+
reactiveMentorship: { enabled: false, triggerOnError: false, errorSeverityThreshold: 'medium', enableKeywords: false },
|
|
1838
|
+
});
|
|
1839
|
+
addItem({
|
|
1840
|
+
type: MessageType.Info,
|
|
1841
|
+
infoContent: 'Mentorship disabled',
|
|
1842
|
+
}, Date.now());
|
|
1843
|
+
return { handled: true };
|
|
1844
|
+
}
|
|
1845
|
+
// /mentorship config - show interactive config menu
|
|
1846
|
+
if (subcommand === 'config') {
|
|
1847
|
+
const menuDef = mentorshipSvc.getMenuDefinition();
|
|
1848
|
+
const config = mentorshipSvc.getConfig();
|
|
1849
|
+
// Extract initial values from config matching menu item keys
|
|
1850
|
+
const initialValues = {};
|
|
1851
|
+
for (const section of menuDef.sections) {
|
|
1852
|
+
for (const item of section.items) {
|
|
1853
|
+
// Map menu item keys (ENV var names) to config values
|
|
1854
|
+
const configKeyMap = {
|
|
1855
|
+
'MENTORSHIP_ENABLED': 'enabled',
|
|
1856
|
+
'MENTORSHIP_TRIGGER_ON_ERROR': 'triggerOnError',
|
|
1857
|
+
'MENTORSHIP_ERROR_THRESHOLD': 'errorThreshold',
|
|
1858
|
+
'MENTORSHIP_KEYWORDS_ENABLED': 'keywordsEnabled',
|
|
1859
|
+
'MENTORSHIP_CUSTOM_KEYWORDS': 'customKeywords',
|
|
1860
|
+
'MENTORSHIP_HELPER_MODEL': 'helperModel',
|
|
1861
|
+
'MENTORSHIP_TURN_BASED_ENABLED': 'turnBasedEnabled',
|
|
1862
|
+
'MENTORSHIP_TURN_INTERVAL': 'turnInterval',
|
|
1863
|
+
'MENTORSHIP_INTERLEAVED_THINKING': 'interleavedThinking',
|
|
1864
|
+
'MENTORSHIP_PATTERN_DETECTION': 'patternDetection',
|
|
1865
|
+
'MENTORSHIP_PATTERN_THRESHOLD': 'patternThreshold',
|
|
1866
|
+
};
|
|
1867
|
+
const configKey = configKeyMap[item.key];
|
|
1868
|
+
if (configKey) {
|
|
1869
|
+
initialValues[item.key] = config[configKey];
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
// Store service and show menu
|
|
1874
|
+
setMentorshipService(mentorshipSvc);
|
|
1875
|
+
setMentorshipMenuDef(menuDef);
|
|
1876
|
+
setMentorshipInitialValues(initialValues);
|
|
1877
|
+
setShowMentorshipConfig(true);
|
|
1878
|
+
return { handled: true };
|
|
1879
|
+
}
|
|
1880
|
+
addItem({
|
|
1881
|
+
type: MessageType.Error,
|
|
1882
|
+
errorContent: 'Usage: /mentorship [status|enable|disable|config]',
|
|
1883
|
+
}, Date.now());
|
|
1884
|
+
return { handled: true };
|
|
1885
|
+
}
|
|
1886
|
+
case 'about': {
|
|
1887
|
+
const { renderSplashScreen: renderFullSplash } = await import('../ui/SplashScreen.js');
|
|
1888
|
+
addItem({ type: MessageType.Info, infoContent: renderFullSplash(undefined, 'neon') }, Date.now());
|
|
1889
|
+
return { handled: true };
|
|
1890
|
+
}
|
|
1891
|
+
case 'agent': {
|
|
1892
|
+
const { AgentStore } = await import('@nexus-cortex/core');
|
|
1893
|
+
const agentDir = path.join(projectPath || process.cwd(), '.cortex', 'agents');
|
|
1894
|
+
const agentStore = new AgentStore({ projectDir: agentDir, enableWatching: false });
|
|
1895
|
+
await agentStore.initialize();
|
|
1896
|
+
try {
|
|
1897
|
+
const agents = agentStore.getAll();
|
|
1898
|
+
if (subcommand === 'list' || !subcommand) {
|
|
1899
|
+
if (agents.length === 0) {
|
|
1900
|
+
addItem({ type: MessageType.Info, infoContent: 'No agents found. Create one in .cortex/agents/' }, Date.now());
|
|
1901
|
+
}
|
|
1902
|
+
else {
|
|
1903
|
+
const lines = agents.map((a) => {
|
|
1904
|
+
const loc = 'location' in a ? a.location === 'project' ? '[P]' : '[G]' : '';
|
|
1905
|
+
const toolsInfo = a.tools === 'all' ? 'all' : Array.isArray(a.tools) ? `${a.tools.length} tools` : '';
|
|
1906
|
+
return ` ${loc} ${a.name} — ${a.description || '(no description)'} (${a.model || 'default'}, ${toolsInfo})`;
|
|
1907
|
+
});
|
|
1908
|
+
addItem({ type: MessageType.Info, infoContent: `Agents (${agents.length}):\n${lines.join('\n')}` }, Date.now());
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
else if (subcommand === 'info' && args.length > 0) {
|
|
1912
|
+
const agent = agentStore.getAgent(args[0]);
|
|
1913
|
+
if (agent) {
|
|
1914
|
+
const toolsList = agent.tools === 'all' ? 'all' : Array.isArray(agent.tools) ? agent.tools.join(', ') : '';
|
|
1915
|
+
const details = [
|
|
1916
|
+
`Name: ${agent.name}`,
|
|
1917
|
+
`Description: ${agent.description || '(none)'}`,
|
|
1918
|
+
`Model: ${agent.model || '(default)'}`,
|
|
1919
|
+
`Tools: ${toolsList}`,
|
|
1920
|
+
'location' in agent ? `Location: ${agent.location}` : null,
|
|
1921
|
+
'filePath' in agent ? `File: ${agent.filePath}` : null,
|
|
1922
|
+
].filter(Boolean).join('\n');
|
|
1923
|
+
addItem({ type: MessageType.Info, infoContent: details }, Date.now());
|
|
1924
|
+
}
|
|
1925
|
+
else {
|
|
1926
|
+
addItem({ type: MessageType.Error, errorContent: `Agent "${args[0]}" not found` }, Date.now());
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
else {
|
|
1930
|
+
addItem({ type: MessageType.Info, infoContent: 'Usage: /agent [list|info <name>]' }, Date.now());
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
finally {
|
|
1934
|
+
await agentStore.destroy();
|
|
1935
|
+
}
|
|
1936
|
+
return { handled: true };
|
|
1937
|
+
}
|
|
1938
|
+
default:
|
|
1939
|
+
addItem({
|
|
1940
|
+
type: MessageType.Error,
|
|
1941
|
+
errorContent: `Unknown command: /${command}. Type /help for available commands.`,
|
|
1942
|
+
}, Date.now());
|
|
1943
|
+
return { handled: true };
|
|
1944
|
+
}
|
|
1945
|
+
}, [parseSlashCommand, clearHistory, addItem, exit, autoApprove, showThinking, currentModel, debug, client, projectPath]);
|
|
1946
|
+
// Cancel handler
|
|
1947
|
+
const handleCancelSubmit = useCallback((_shouldRestorePrompt) => {
|
|
1948
|
+
handleDebugMessage('Request cancelled');
|
|
1949
|
+
}, [handleDebugMessage]);
|
|
1950
|
+
// Streaming hook
|
|
1951
|
+
const { streamingState, submitQuery, initError: _streamInitError, pendingHistoryItems: _pendingHistoryItems, thought: _thought, cancelOngoingRequest, pendingToolCalls: _pendingToolCalls, pendingToolInputArgs: _pendingToolInputArgs, handleApprovalModeChange, streamEvents, turnSummary, turnUsage: _turnUsage, nextActionPrediction, streamStartTime, } = useCortexStream(client, history, addItem, { debug, projectPath, autoApprove }, { showThinking }, handleDebugMessage, handleSlashCommand, false, // shellModeActive
|
|
1952
|
+
handleCancelSubmit, terminalWidth, terminalHeight, refreshOrchestratorHistory);
|
|
1953
|
+
// Keyboard shortcuts
|
|
1954
|
+
useInput((input, key) => {
|
|
1955
|
+
// Tab - toggle extended reasoning (only for toggleable models)
|
|
1956
|
+
if (key.tab && !key.shift) {
|
|
1957
|
+
if (reasoningToggleable) {
|
|
1958
|
+
// Toggle extended reasoning on/off
|
|
1959
|
+
setReasoningEffort(prev => {
|
|
1960
|
+
const newEffort = prev === 'none' ? 'high' : 'none';
|
|
1961
|
+
addItem({
|
|
1962
|
+
type: MessageType.Info,
|
|
1963
|
+
infoContent: `Extended reasoning: ${newEffort === 'high' ? 'ON' : 'OFF'}`,
|
|
1964
|
+
}, Date.now());
|
|
1965
|
+
return newEffort;
|
|
1966
|
+
});
|
|
1967
|
+
}
|
|
1968
|
+
else {
|
|
1969
|
+
// Model has native interleaved thinking or no reasoning support
|
|
1970
|
+
addItem({
|
|
1971
|
+
type: MessageType.Info,
|
|
1972
|
+
infoContent: supportsReasoning
|
|
1973
|
+
? `${currentModel} has native interleaved thinking (always visible)`
|
|
1974
|
+
: `${currentModel} does not support extended reasoning`,
|
|
1975
|
+
}, Date.now());
|
|
1976
|
+
}
|
|
1977
|
+
return;
|
|
1978
|
+
}
|
|
1979
|
+
// Shift+Tab - toggle auto-approve
|
|
1980
|
+
if (key.tab && key.shift) {
|
|
1981
|
+
setAutoApprove(prev => {
|
|
1982
|
+
const newValue = !prev;
|
|
1983
|
+
handleApprovalModeChange(newValue);
|
|
1984
|
+
return newValue;
|
|
1985
|
+
});
|
|
1986
|
+
return;
|
|
1987
|
+
}
|
|
1988
|
+
// ESC - cancel streaming or close dialogs
|
|
1989
|
+
if (key.escape) {
|
|
1990
|
+
if (showHelp) {
|
|
1991
|
+
setShowHelp(false);
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
if (showConfigMenu) {
|
|
1995
|
+
setShowConfigMenu(false);
|
|
1996
|
+
return;
|
|
1997
|
+
}
|
|
1998
|
+
if (streamingState === StreamingState.Streaming) {
|
|
1999
|
+
cancelOngoingRequest();
|
|
2000
|
+
return;
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
// Ctrl+C - exit
|
|
2004
|
+
if (key.ctrl && input === 'c') {
|
|
2005
|
+
exit();
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
// Ctrl+E - toggle document previews (expand/collapse)
|
|
2009
|
+
if (key.ctrl && input === 'e' && streamingState === StreamingState.Idle) {
|
|
2010
|
+
// Collect all document IDs from history
|
|
2011
|
+
const allDocIds = [];
|
|
2012
|
+
orchestratorHistory.forEach((msg) => {
|
|
2013
|
+
const content = msg.message?.content;
|
|
2014
|
+
if (Array.isArray(content)) {
|
|
2015
|
+
content.forEach((block, i) => {
|
|
2016
|
+
if (block.type === 'tool_result' && block.metadata?.documentPreview) {
|
|
2017
|
+
allDocIds.push({
|
|
2018
|
+
id: `${msg.uuid}-doc-${i}`,
|
|
2019
|
+
filePath: block.metadata.documentPreview.filePath,
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
2024
|
+
});
|
|
2025
|
+
if (allDocIds.length === 0) {
|
|
2026
|
+
// No documents to toggle
|
|
2027
|
+
return;
|
|
2028
|
+
}
|
|
2029
|
+
// Toggle all documents: if any expanded, collapse all; otherwise expand all
|
|
2030
|
+
setExpandedDocuments(prev => {
|
|
2031
|
+
if (prev.size > 0) {
|
|
2032
|
+
// Collapse all
|
|
2033
|
+
return new Set();
|
|
2034
|
+
}
|
|
2035
|
+
else {
|
|
2036
|
+
// Expand all and lazy-load content
|
|
2037
|
+
const newSet = new Set(allDocIds.map(d => d.id));
|
|
2038
|
+
// Trigger lazy loading for all documents
|
|
2039
|
+
allDocIds.forEach(({ id, filePath }) => {
|
|
2040
|
+
if (!documentContents.has(id)) {
|
|
2041
|
+
// Resolve the file path (it's relative, need to make absolute)
|
|
2042
|
+
const absolutePath = path.isAbsolute(filePath)
|
|
2043
|
+
? filePath
|
|
2044
|
+
: path.resolve(process.cwd(), filePath);
|
|
2045
|
+
// Read file content asynchronously
|
|
2046
|
+
fs.promises.readFile(absolutePath, 'utf-8').then((content) => {
|
|
2047
|
+
setDocumentContents(prev => new Map(prev).set(id, content));
|
|
2048
|
+
}).catch(() => {
|
|
2049
|
+
setDocumentContents(prev => new Map(prev).set(id, null));
|
|
2050
|
+
});
|
|
2051
|
+
}
|
|
2052
|
+
});
|
|
2053
|
+
return newSet;
|
|
2054
|
+
}
|
|
2055
|
+
});
|
|
2056
|
+
addItem({
|
|
2057
|
+
type: MessageType.Info,
|
|
2058
|
+
infoContent: `Document previews: ${expandedDocuments.size > 0 ? 'collapsed' : 'expanded'}`,
|
|
2059
|
+
}, Date.now());
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
2062
|
+
});
|
|
2063
|
+
// Use refs for values that change during streaming to avoid invalidating memo
|
|
2064
|
+
const streamingStateRef = useRef(streamingState);
|
|
2065
|
+
const reasoningEffortRef = useRef(reasoningEffort);
|
|
2066
|
+
const supportsReasoningRef = useRef(supportsReasoning);
|
|
2067
|
+
useEffect(() => {
|
|
2068
|
+
streamingStateRef.current = streamingState;
|
|
2069
|
+
reasoningEffortRef.current = reasoningEffort;
|
|
2070
|
+
supportsReasoningRef.current = supportsReasoning;
|
|
2071
|
+
}, [streamingState, reasoningEffort, supportsReasoning]);
|
|
2072
|
+
// Handle message submission - stable callback for EnhancedInput memo
|
|
2073
|
+
const handleSubmit = useCallback(async (text, prefillProvenance = 'none') => {
|
|
2074
|
+
// If currently streaming, queue the message for later (provenance rides
|
|
2075
|
+
// the queue — though ghosts only render at Idle, so it's near-always 'none')
|
|
2076
|
+
if (streamingStateRef.current === StreamingState.Streaming) {
|
|
2077
|
+
setQueuedMessage({ text, prefillProvenance });
|
|
2078
|
+
addItem({
|
|
2079
|
+
type: MessageType.Info,
|
|
2080
|
+
infoContent: ` Message queued: "${text.slice(0, 50)}${text.length > 50 ? '...' : ''}"`,
|
|
2081
|
+
}, Date.now());
|
|
2082
|
+
return;
|
|
2083
|
+
}
|
|
2084
|
+
// Pass reasoning effort for models that support it
|
|
2085
|
+
await submitQuery(text, {
|
|
2086
|
+
reasoningEffort: supportsReasoningRef.current ? reasoningEffortRef.current : undefined,
|
|
2087
|
+
prefillProvenance,
|
|
2088
|
+
});
|
|
2089
|
+
}, [submitQuery, addItem]); // Minimal dependencies for stable reference
|
|
2090
|
+
// Auto-send initial prompt (from --prompt flag) once initialized
|
|
2091
|
+
const hasSentInitialPrompt = useRef(false);
|
|
2092
|
+
useEffect(() => {
|
|
2093
|
+
if (initialPrompt && initialized && !hasSentInitialPrompt.current) {
|
|
2094
|
+
hasSentInitialPrompt.current = true;
|
|
2095
|
+
handleSubmit(initialPrompt);
|
|
2096
|
+
}
|
|
2097
|
+
}, [initialPrompt, initialized, handleSubmit]);
|
|
2098
|
+
// Process queued message when streaming completes
|
|
2099
|
+
useEffect(() => {
|
|
2100
|
+
if (streamingState === StreamingState.Idle && queuedMessage) {
|
|
2101
|
+
const message = queuedMessage;
|
|
2102
|
+
setQueuedMessage(null);
|
|
2103
|
+
addItem({
|
|
2104
|
+
type: MessageType.Info,
|
|
2105
|
+
infoContent: `▶ Sending queued message...`,
|
|
2106
|
+
}, Date.now());
|
|
2107
|
+
// Submit directly - the state is already Idle
|
|
2108
|
+
submitQuery(message.text, {
|
|
2109
|
+
reasoningEffort: supportsReasoning ? reasoningEffort : undefined,
|
|
2110
|
+
prefillProvenance: message.prefillProvenance,
|
|
2111
|
+
});
|
|
2112
|
+
}
|
|
2113
|
+
}, [streamingState, queuedMessage, submitQuery, supportsReasoning, reasoningEffort, addItem]);
|
|
2114
|
+
// Loading state - show simple spinner while initializing
|
|
2115
|
+
if (!initialized) {
|
|
2116
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { color: Colors.AccentCyan, children: "CORTEX" }), _jsx(LoadingSpinner, { text: "Initializing..." }), initError && _jsxs(Text, { color: Colors.AccentRed, children: ["Error: ", initError] })] }));
|
|
2117
|
+
}
|
|
2118
|
+
// Help screen - shows all commands from unified registry
|
|
2119
|
+
if (showHelp) {
|
|
2120
|
+
const allCommands = slashCommandRegistry.getAllCommands();
|
|
2121
|
+
const categories = slashCommandRegistry.getCategories();
|
|
2122
|
+
// Group commands by category
|
|
2123
|
+
const grouped = {};
|
|
2124
|
+
for (const cmd of allCommands) {
|
|
2125
|
+
if (!grouped[cmd.category])
|
|
2126
|
+
grouped[cmd.category] = [];
|
|
2127
|
+
grouped[cmd.category].push(cmd);
|
|
2128
|
+
}
|
|
2129
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Text, { bold: true, color: Colors.AccentCyan, children: ["\u2501\u2501\u2501 CORTEX Help (", allCommands.length, " commands) \u2501\u2501\u2501"] }), _jsx(Text, {}), categories.map((category) => {
|
|
2130
|
+
const cmds = grouped[category.name];
|
|
2131
|
+
if (!cmds || cmds.length === 0)
|
|
2132
|
+
return null;
|
|
2133
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, color: Colors.AccentGreen, children: [category.icon, " ", category.label, ":"] }), cmds.map((cmd) => (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsxs(Text, { color: Colors.AccentCyan, children: [" /", cmd.name] }), cmd.altName && _jsxs(Text, { dimColor: true, children: [" (/", cmd.altName, ")"] }), _jsxs(Text, { dimColor: true, children: [" ", cmd.description] })] }), cmd.subcommands && cmd.subcommands.map((sub) => (_jsxs(Text, { children: [_jsxs(Text, { color: Colors.Gray, children: [" ", sub.name] }), sub.altName && _jsxs(Text, { dimColor: true, children: [" (", sub.altName, ")"] }), _jsxs(Text, { dimColor: true, children: [" ", sub.description] })] }, sub.name)))] }, cmd.name))), _jsx(Text, {})] }, category.name));
|
|
2134
|
+
}), _jsx(Text, { bold: true, color: Colors.AccentYellow, children: "Keyboard Shortcuts:" }), _jsx(Text, { children: " Tab Toggle thinking display" }), _jsx(Text, { children: " Shift+Tab Toggle auto-approve (YOLO)" }), _jsx(Text, { children: " Ctrl+E Expand/collapse document previews" }), _jsx(Text, { children: " ESC Cancel streaming / Close dialogs" }), _jsx(Text, { children: " Ctrl+C Exit" }), _jsx(Text, {}), _jsx(Text, { bold: true, color: Colors.AccentYellow, children: "Input Features:" }), _jsx(Text, { children: " \u2191/\u2193 Navigate message history" }), _jsx(Text, { children: " Shift+Enter Multi-line input (new line)" }), _jsx(Text, { children: " Enter Submit message" }), _jsx(Text, { children: " ESC Clear input / Cancel history" }), _jsx(Text, {}), _jsx(Text, { dimColor: true, children: "Press ESC to close" })] }));
|
|
2135
|
+
}
|
|
2136
|
+
if (showConfigMenu) {
|
|
2137
|
+
return (_jsx(ConfigMenu, { projectPath: projectPath || process.cwd(), onUpdateRuntimeConfig: client ? (updates) => client.updateRuntimeConfig(updates) : undefined, onClose: () => setShowConfigMenu(false) }));
|
|
2138
|
+
}
|
|
2139
|
+
// Main UI - Using Static for history to prevent re-renders on input
|
|
2140
|
+
return (_jsxs(Box, { flexDirection: "column", width: terminalWidth, children: [history.length === 0 && streamingState === StreamingState.Idle && (_jsx(CompactHeader, { model: currentModel, cwd: projectPath || process.cwd() })), debug && (_jsxs(Text, { dimColor: true, children: ["[Debug: ", history.length, " messages in history]"] })), _jsx(Static, { items: history, children: (item) => (_jsx(MessageDisplay, { item: item, terminalWidth: terminalWidth, showThinking: showThinking, expandedDocuments: expandedDocuments, documentContents: documentContents }, item.id)) }, `history-exp-${expandedDocuments.size}-content-${documentContents.size}`), _jsxs(Box, { flexDirection: "column", children: [streamingState === StreamingState.Streaming && (_jsx(StreamDisplay, { events: streamEvents, isStreaming: true, showThinking: showThinking, streamStartTime: streamStartTime })), subAgents.size > 0 && (_jsx(SubAgentPanel, { agents: subAgents, terminalWidth: terminalWidth })), pendingApproval && (_jsx(ApprovalDialog, { pendingApproval: pendingApproval, onApprove: approve, onDeny: deny, onApproveAndYolo: approveAndEnableYolo })), showThemePicker && (_jsx(ThemePickerDialog, { themes: getThemeNames(), currentTheme: currentTheme, onSelect: (theme) => {
|
|
2141
|
+
if (setTheme(theme)) {
|
|
2142
|
+
setCurrentTheme(theme);
|
|
2143
|
+
const themeDef = getThemeDefinition(theme);
|
|
2144
|
+
addItem({
|
|
2145
|
+
type: MessageType.Info,
|
|
2146
|
+
infoContent: `✓ Theme set to: ${themeDef?.name || theme}`,
|
|
2147
|
+
}, Date.now());
|
|
2148
|
+
}
|
|
2149
|
+
setShowThemePicker(false);
|
|
2150
|
+
}, onCancel: () => setShowThemePicker(false) })), showModelPicker && (_jsx(ModelPickerDialog, { models: availableModels, currentModelId: client?.getCurrentModel()?.id || currentModel, loading: modelsLoading, onSelect: async (selectedModelId) => {
|
|
2151
|
+
if (client) {
|
|
2152
|
+
try {
|
|
2153
|
+
await client.switchModel(selectedModelId);
|
|
2154
|
+
const newModel = client.getCurrentModel();
|
|
2155
|
+
const modelSupportsReasoning = newModel?.reasoning?.supported === true;
|
|
2156
|
+
const modelReasoningToggleable = newModel?.reasoning?.toggleable === true;
|
|
2157
|
+
setCurrentModel(newModel?.name || newModel?.id || selectedModelId);
|
|
2158
|
+
setSupportsReasoning(modelSupportsReasoning);
|
|
2159
|
+
setReasoningToggleable(modelReasoningToggleable);
|
|
2160
|
+
// Reset reasoning effort when switching models
|
|
2161
|
+
if (modelSupportsReasoning && modelReasoningToggleable) {
|
|
2162
|
+
setReasoningEffort(showThinking ? 'high' : 'none');
|
|
2163
|
+
}
|
|
2164
|
+
else {
|
|
2165
|
+
setReasoningEffort('none');
|
|
2166
|
+
}
|
|
2167
|
+
// Persist model choice to .cortex/config.json
|
|
2168
|
+
persistModel(selectedModelId);
|
|
2169
|
+
addItem({
|
|
2170
|
+
type: MessageType.Info,
|
|
2171
|
+
infoContent: `✓ Switched to model: ${newModel?.name || selectedModelId}${modelSupportsReasoning ? ' (supports reasoning)' : ''}\n (saved as default)`,
|
|
2172
|
+
}, Date.now());
|
|
2173
|
+
}
|
|
2174
|
+
catch (error) {
|
|
2175
|
+
addItem({
|
|
2176
|
+
type: MessageType.Error,
|
|
2177
|
+
errorContent: `Error switching model: ${error.message}`,
|
|
2178
|
+
}, Date.now());
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
setShowModelPicker(false);
|
|
2182
|
+
}, onCancel: () => setShowModelPicker(false) })), showSessionPicker && (_jsx(SessionPickerDialog, { sessions: availableSessions, loading: sessionsLoading, onSelect: async (sessionId) => {
|
|
2183
|
+
if (client) {
|
|
2184
|
+
try {
|
|
2185
|
+
addItem({ type: MessageType.Info, infoContent: `Resuming session ${sessionId.slice(0, 8)}...` }, Date.now());
|
|
2186
|
+
await client.resumeSession(sessionId);
|
|
2187
|
+
// Refresh history to display the loaded session's messages
|
|
2188
|
+
refreshOrchestratorHistory();
|
|
2189
|
+
addItem({ type: MessageType.Info, infoContent: `✓ Session resumed. Continue your conversation.` }, Date.now());
|
|
2190
|
+
}
|
|
2191
|
+
catch (error) {
|
|
2192
|
+
addItem({
|
|
2193
|
+
type: MessageType.Error,
|
|
2194
|
+
errorContent: `Error resuming session: ${error.message}`,
|
|
2195
|
+
}, Date.now());
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
setShowSessionPicker(false);
|
|
2199
|
+
}, onCancel: () => setShowSessionPicker(false) })), showSystemMessageManager && systemMessageStore && (_jsx(InteractiveMenu, { store: systemMessageStore, runtimeDir: systemMessageStore.getRuntimeDir(), builtinDir: systemMessageStore.getBuiltinDir(), onExit: async () => {
|
|
2200
|
+
setShowSystemMessageManager(false);
|
|
2201
|
+
// Cleanup store
|
|
2202
|
+
if (systemMessageStore) {
|
|
2203
|
+
await systemMessageStore.destroy();
|
|
2204
|
+
setSystemMessageStore(null);
|
|
2205
|
+
}
|
|
2206
|
+
addItem({
|
|
2207
|
+
type: MessageType.Info,
|
|
2208
|
+
infoContent: '✓ System Message Manager closed',
|
|
2209
|
+
}, Date.now());
|
|
2210
|
+
} })), showMentorshipConfig && mentorshipMenuDef && mentorshipService && (_jsx(MenuRenderer, { definition: mentorshipMenuDef, initialValues: mentorshipInitialValues, isActive: showMentorshipConfig, onComplete: async (result) => {
|
|
2211
|
+
setShowMentorshipConfig(false);
|
|
2212
|
+
if (result.action === 'save' && result.hasChanges) {
|
|
2213
|
+
// Map menu keys back to config keys and save
|
|
2214
|
+
const configKeyMap = {
|
|
2215
|
+
'MENTORSHIP_ENABLED': 'enabled',
|
|
2216
|
+
'MENTORSHIP_TRIGGER_ON_ERROR': 'triggerOnError',
|
|
2217
|
+
'MENTORSHIP_ERROR_THRESHOLD': 'errorThreshold',
|
|
2218
|
+
'MENTORSHIP_KEYWORDS_ENABLED': 'keywordsEnabled',
|
|
2219
|
+
'MENTORSHIP_CUSTOM_KEYWORDS': 'customKeywords',
|
|
2220
|
+
'MENTORSHIP_HELPER_MODEL': 'helperModel',
|
|
2221
|
+
'MENTORSHIP_TURN_BASED_ENABLED': 'turnBasedEnabled',
|
|
2222
|
+
'MENTORSHIP_TURN_INTERVAL': 'turnInterval',
|
|
2223
|
+
'MENTORSHIP_INTERLEAVED_THINKING': 'interleavedThinking',
|
|
2224
|
+
'MENTORSHIP_PATTERN_DETECTION': 'patternDetection',
|
|
2225
|
+
'MENTORSHIP_PATTERN_THRESHOLD': 'patternThreshold',
|
|
2226
|
+
};
|
|
2227
|
+
const configChanges = {};
|
|
2228
|
+
for (const [menuKey, value] of Object.entries(result.changes)) {
|
|
2229
|
+
const configKey = configKeyMap[menuKey];
|
|
2230
|
+
if (configKey) {
|
|
2231
|
+
configChanges[configKey] = value;
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
try {
|
|
2235
|
+
await mentorshipService.setConfig(configChanges);
|
|
2236
|
+
addItem({
|
|
2237
|
+
type: MessageType.Info,
|
|
2238
|
+
infoContent: `✓ Mentorship config saved (${Object.keys(result.changes).length} changes)`,
|
|
2239
|
+
}, Date.now());
|
|
2240
|
+
}
|
|
2241
|
+
catch (error) {
|
|
2242
|
+
addItem({
|
|
2243
|
+
type: MessageType.Error,
|
|
2244
|
+
errorContent: `Error saving config: ${error.message}`,
|
|
2245
|
+
}, Date.now());
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
else if (result.action === 'reset') {
|
|
2249
|
+
try {
|
|
2250
|
+
await mentorshipService.resetToDefaults();
|
|
2251
|
+
addItem({
|
|
2252
|
+
type: MessageType.Info,
|
|
2253
|
+
infoContent: '✓ Mentorship config reset to defaults',
|
|
2254
|
+
}, Date.now());
|
|
2255
|
+
}
|
|
2256
|
+
catch (error) {
|
|
2257
|
+
addItem({
|
|
2258
|
+
type: MessageType.Error,
|
|
2259
|
+
errorContent: `Error resetting config: ${error.message}`,
|
|
2260
|
+
}, Date.now());
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
else if (result.action === 'cancel') {
|
|
2264
|
+
addItem({
|
|
2265
|
+
type: MessageType.Info,
|
|
2266
|
+
infoContent: 'Mentorship config cancelled',
|
|
2267
|
+
}, Date.now());
|
|
2268
|
+
}
|
|
2269
|
+
// Cleanup
|
|
2270
|
+
setMentorshipService(null);
|
|
2271
|
+
setMentorshipMenuDef(null);
|
|
2272
|
+
setMentorshipInitialValues({});
|
|
2273
|
+
}, onLiveUpdate: async (key, value) => {
|
|
2274
|
+
// Handle live updates for toggles
|
|
2275
|
+
const configKeyMap = {
|
|
2276
|
+
'MENTORSHIP_ENABLED': 'enabled',
|
|
2277
|
+
'MENTORSHIP_TRIGGER_ON_ERROR': 'triggerOnError',
|
|
2278
|
+
'MENTORSHIP_ERROR_THRESHOLD': 'errorThreshold',
|
|
2279
|
+
'MENTORSHIP_KEYWORDS_ENABLED': 'keywordsEnabled',
|
|
2280
|
+
'MENTORSHIP_TURN_BASED_ENABLED': 'turnBasedEnabled',
|
|
2281
|
+
'MENTORSHIP_PATTERN_DETECTION': 'patternDetection',
|
|
2282
|
+
'MENTORSHIP_INTERLEAVED_THINKING': 'interleavedThinking',
|
|
2283
|
+
};
|
|
2284
|
+
const configKey = configKeyMap[key];
|
|
2285
|
+
if (configKey && mentorshipService) {
|
|
2286
|
+
await mentorshipService.setConfig({ [configKey]: value });
|
|
2287
|
+
}
|
|
2288
|
+
} }))] }), queuedMessage && (_jsxs(Box, { paddingX: 1, marginBottom: 1, children: [_jsxs(Text, { color: Colors.AccentYellow, children: ["Queued: \"", queuedMessage.text.slice(0, 60), queuedMessage.text.length > 60 ? '...' : '', "\""] }), _jsx(Text, { dimColor: true, children: " (will send when ready)" })] })), turnSummary && streamingState === StreamingState.Idle && (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { dimColor: true, children: ["[Summary] ", turnSummary] }) })), _jsx(EnhancedInput, { onSubmit: handleSubmit, disabled: !!pendingApproval || showThemePicker || showModelPicker || showSessionPicker || showSystemMessageManager, placeholder: streamingState === StreamingState.Streaming ? "Type next message (will queue)..." : undefined, userMessages: userMessages, prefillValue: streamingState === StreamingState.Idle ? nextActionPrediction : null }), _jsx(StatusLine, { model: currentModel, autoApprove: autoApprove, showThinking: showThinking, reasoningEffort: reasoningEffort, supportsReasoning: supportsReasoning })] }));
|
|
2289
|
+
};
|
|
2290
|
+
export default CortexApp;
|
|
2291
|
+
//# sourceMappingURL=CortexApp.js.map
|