dexto 1.6.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/dist/agents/coding-agent/coding-agent.yml +3 -1
  2. package/dist/cli/assets/sounds/SOURCES.md +35 -0
  3. package/dist/cli/assets/sounds/boot.wav +0 -0
  4. package/dist/cli/assets/sounds/chime.wav +0 -0
  5. package/dist/cli/assets/sounds/coin.wav +0 -0
  6. package/dist/cli/assets/sounds/confirm.wav +0 -0
  7. package/dist/cli/assets/sounds/levelup.wav +0 -0
  8. package/dist/cli/assets/sounds/ping.wav +0 -0
  9. package/dist/cli/assets/sounds/powerup.wav +0 -0
  10. package/dist/cli/assets/sounds/startup.wav +0 -0
  11. package/dist/cli/assets/sounds/success.wav +0 -0
  12. package/dist/cli/assets/sounds/treasure.wav +0 -0
  13. package/dist/cli/assets/sounds/win.wav +0 -0
  14. package/dist/cli/commands/interactive-commands/exit-handler.d.ts +12 -0
  15. package/dist/cli/commands/interactive-commands/exit-handler.d.ts.map +1 -0
  16. package/dist/cli/commands/interactive-commands/exit-handler.js +20 -0
  17. package/dist/cli/commands/interactive-commands/exit-stats.d.ts +24 -0
  18. package/dist/cli/commands/interactive-commands/exit-stats.d.ts.map +1 -0
  19. package/dist/cli/commands/interactive-commands/exit-stats.js +17 -0
  20. package/dist/cli/commands/interactive-commands/general-commands.d.ts.map +1 -1
  21. package/dist/cli/commands/interactive-commands/general-commands.js +53 -3
  22. package/dist/cli/commands/interactive-commands/prompt-commands.d.ts.map +1 -1
  23. package/dist/cli/commands/interactive-commands/prompt-commands.js +12 -67
  24. package/dist/cli/commands/interactive-commands/session/session-commands.d.ts.map +1 -1
  25. package/dist/cli/commands/interactive-commands/session/session-commands.js +0 -2
  26. package/dist/cli/commands/interactive-commands/system/system-commands.d.ts +1 -13
  27. package/dist/cli/commands/interactive-commands/system/system-commands.d.ts.map +1 -1
  28. package/dist/cli/commands/interactive-commands/system/system-commands.js +45 -54
  29. package/dist/cli/ink-cli/InkCLIRefactored.d.ts.map +1 -1
  30. package/dist/cli/ink-cli/InkCLIRefactored.js +132 -21
  31. package/dist/cli/ink-cli/components/ApprovalPrompt.d.ts.map +1 -1
  32. package/dist/cli/ink-cli/components/ApprovalPrompt.js +74 -20
  33. package/dist/cli/ink-cli/components/ElicitationForm.d.ts +5 -3
  34. package/dist/cli/ink-cli/components/ElicitationForm.d.ts.map +1 -1
  35. package/dist/cli/ink-cli/components/ElicitationForm.js +414 -180
  36. package/dist/cli/ink-cli/components/ResourceAutocomplete.d.ts.map +1 -1
  37. package/dist/cli/ink-cli/components/ResourceAutocomplete.js +20 -11
  38. package/dist/cli/ink-cli/components/SlashCommandAutocomplete.d.ts.map +1 -1
  39. package/dist/cli/ink-cli/components/SlashCommandAutocomplete.js +47 -67
  40. package/dist/cli/ink-cli/components/StatusBar.d.ts.map +1 -1
  41. package/dist/cli/ink-cli/components/StatusBar.js +10 -4
  42. package/dist/cli/ink-cli/components/base/BaseSelector.d.ts +2 -1
  43. package/dist/cli/ink-cli/components/base/BaseSelector.d.ts.map +1 -1
  44. package/dist/cli/ink-cli/components/base/BaseSelector.js +37 -27
  45. package/dist/cli/ink-cli/components/chat/Header.d.ts.map +1 -1
  46. package/dist/cli/ink-cli/components/chat/Header.js +1 -1
  47. package/dist/cli/ink-cli/components/chat/MessageItem.d.ts.map +1 -1
  48. package/dist/cli/ink-cli/components/chat/MessageItem.js +3 -1
  49. package/dist/cli/ink-cli/components/chat/ToolIcon.d.ts.map +1 -1
  50. package/dist/cli/ink-cli/components/chat/ToolIcon.js +5 -15
  51. package/dist/cli/ink-cli/components/chat/styled-boxes/LogConfigBox.d.ts.map +1 -1
  52. package/dist/cli/ink-cli/components/chat/styled-boxes/LogConfigBox.js +1 -1
  53. package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.d.ts.map +1 -1
  54. package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.js +4 -2
  55. package/dist/cli/ink-cli/components/modes/StaticCLI.d.ts.map +1 -1
  56. package/dist/cli/ink-cli/components/modes/StaticCLI.js +9 -2
  57. package/dist/cli/ink-cli/components/overlays/CommandOutputOverlay.d.ts +13 -0
  58. package/dist/cli/ink-cli/components/overlays/CommandOutputOverlay.d.ts.map +1 -0
  59. package/dist/cli/ink-cli/components/overlays/CommandOutputOverlay.js +60 -0
  60. package/dist/cli/ink-cli/components/overlays/LogLevelSelector.js +1 -1
  61. package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.d.ts.map +1 -1
  62. package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.js +213 -100
  63. package/dist/cli/ink-cli/components/overlays/PromptList.d.ts.map +1 -1
  64. package/dist/cli/ink-cli/components/overlays/PromptList.js +12 -16
  65. package/dist/cli/ink-cli/components/overlays/SoundsSelector.d.ts +21 -0
  66. package/dist/cli/ink-cli/components/overlays/SoundsSelector.d.ts.map +1 -0
  67. package/dist/cli/ink-cli/components/overlays/SoundsSelector.js +566 -0
  68. package/dist/cli/ink-cli/components/overlays/ToolBrowser.d.ts.map +1 -1
  69. package/dist/cli/ink-cli/components/overlays/ToolBrowser.js +94 -39
  70. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/LocalModelWizard.d.ts.map +1 -1
  71. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/LocalModelWizard.js +8 -13
  72. package/dist/cli/ink-cli/components/renderers/FilePreviewRenderer.d.ts +3 -3
  73. package/dist/cli/ink-cli/components/renderers/FilePreviewRenderer.d.ts.map +1 -1
  74. package/dist/cli/ink-cli/components/renderers/FilePreviewRenderer.js +6 -5
  75. package/dist/cli/ink-cli/components/renderers/FileRenderer.d.ts +3 -1
  76. package/dist/cli/ink-cli/components/renderers/FileRenderer.d.ts.map +1 -1
  77. package/dist/cli/ink-cli/components/renderers/FileRenderer.js +18 -7
  78. package/dist/cli/ink-cli/components/renderers/ShellRenderer.d.ts.map +1 -1
  79. package/dist/cli/ink-cli/components/renderers/ShellRenderer.js +7 -17
  80. package/dist/cli/ink-cli/components/renderers/index.d.ts.map +1 -1
  81. package/dist/cli/ink-cli/components/renderers/index.js +1 -1
  82. package/dist/cli/ink-cli/components/shared/FocusOverlayFrame.d.ts +7 -0
  83. package/dist/cli/ink-cli/components/shared/FocusOverlayFrame.d.ts.map +1 -0
  84. package/dist/cli/ink-cli/components/shared/FocusOverlayFrame.js +8 -0
  85. package/dist/cli/ink-cli/components/shared/HintBar.d.ts +6 -0
  86. package/dist/cli/ink-cli/components/shared/HintBar.d.ts.map +1 -0
  87. package/dist/cli/ink-cli/components/shared/HintBar.js +6 -0
  88. package/dist/cli/ink-cli/constants/spinnerFrames.d.ts +2 -0
  89. package/dist/cli/ink-cli/constants/spinnerFrames.d.ts.map +1 -0
  90. package/dist/cli/ink-cli/constants/spinnerFrames.js +1 -0
  91. package/dist/cli/ink-cli/constants/tips.d.ts.map +1 -1
  92. package/dist/cli/ink-cli/constants/tips.js +1 -0
  93. package/dist/cli/ink-cli/containers/InputContainer.d.ts.map +1 -1
  94. package/dist/cli/ink-cli/containers/InputContainer.js +19 -15
  95. package/dist/cli/ink-cli/containers/OverlayContainer.d.ts.map +1 -1
  96. package/dist/cli/ink-cli/containers/OverlayContainer.js +21 -5
  97. package/dist/cli/ink-cli/hooks/useAnimationTick.d.ts +11 -0
  98. package/dist/cli/ink-cli/hooks/useAnimationTick.d.ts.map +1 -0
  99. package/dist/cli/ink-cli/hooks/useAnimationTick.js +54 -0
  100. package/dist/cli/ink-cli/hooks/useCLIState.d.ts.map +1 -1
  101. package/dist/cli/ink-cli/hooks/useCLIState.js +1 -0
  102. package/dist/cli/ink-cli/services/processStream.d.ts.map +1 -1
  103. package/dist/cli/ink-cli/services/processStream.js +17 -8
  104. package/dist/cli/ink-cli/state/initialState.d.ts.map +1 -1
  105. package/dist/cli/ink-cli/state/initialState.js +1 -0
  106. package/dist/cli/ink-cli/state/types.d.ts +13 -1
  107. package/dist/cli/ink-cli/state/types.d.ts.map +1 -1
  108. package/dist/cli/ink-cli/utils/commandOverlays.d.ts.map +1 -1
  109. package/dist/cli/ink-cli/utils/commandOverlays.js +1 -0
  110. package/dist/cli/ink-cli/utils/elicitationSchema.d.ts +11 -0
  111. package/dist/cli/ink-cli/utils/elicitationSchema.d.ts.map +1 -0
  112. package/dist/cli/ink-cli/utils/elicitationSchema.js +80 -0
  113. package/dist/cli/ink-cli/utils/index.d.ts +1 -1
  114. package/dist/cli/ink-cli/utils/index.d.ts.map +1 -1
  115. package/dist/cli/ink-cli/utils/index.js +1 -1
  116. package/dist/cli/ink-cli/utils/messageFormatting.d.ts +2 -17
  117. package/dist/cli/ink-cli/utils/messageFormatting.d.ts.map +1 -1
  118. package/dist/cli/ink-cli/utils/messageFormatting.js +22 -128
  119. package/dist/cli/ink-cli/utils/overlayPresentation.d.ts +19 -0
  120. package/dist/cli/ink-cli/utils/overlayPresentation.d.ts.map +1 -0
  121. package/dist/cli/ink-cli/utils/overlayPresentation.js +33 -0
  122. package/dist/cli/ink-cli/utils/overlaySizing.d.ts +19 -0
  123. package/dist/cli/ink-cli/utils/overlaySizing.d.ts.map +1 -0
  124. package/dist/cli/ink-cli/utils/overlaySizing.js +11 -0
  125. package/dist/cli/ink-cli/utils/soundNotification.d.ts +19 -13
  126. package/dist/cli/ink-cli/utils/soundNotification.d.ts.map +1 -1
  127. package/dist/cli/ink-cli/utils/soundNotification.js +120 -97
  128. package/dist/utils/session-logger-factory.d.ts.map +1 -1
  129. package/dist/utils/session-logger-factory.js +17 -2
  130. package/dist/webui/assets/{index-DwtueA8l.js → index-CKhumsZA.js} +135 -135
  131. package/dist/webui/index.html +1 -1
  132. package/package.json +11 -11
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /**
3
3
  * AlternateBufferCLI - VirtualizedList rendering mode
4
4
  *
@@ -30,6 +30,7 @@ import { VirtualizedList, SCROLL_TO_ITEM_END, } from '../shared/VirtualizedList.
30
30
  // Containers
31
31
  import { InputContainer } from '../../containers/InputContainer.js';
32
32
  import { OverlayContainer } from '../../containers/OverlayContainer.js';
33
+ import { shouldHideStatusChrome } from '../../utils/overlayPresentation.js';
33
34
  export function AlternateBufferCLI({ agent, initialSessionId, initialPrompt, startupInfo, configFilePath, onSelectionAttempt, useStreaming = true, }) {
34
35
  // Refs for VirtualizedList
35
36
  const listRef = useRef(null);
@@ -176,5 +177,6 @@ export function AlternateBufferCLI({ agent, initialSessionId, initialPrompt, sta
176
177
  return 'header';
177
178
  return item.message.id;
178
179
  }, []);
179
- return (_jsxs(Box, { flexDirection: "column", height: terminalHeight, children: [_jsx(Box, { ref: listContainerRef, flexGrow: 1, flexShrink: 1, minHeight: 0, children: _jsx(VirtualizedList, { ref: listRef, data: listData, renderItem: renderListItem, estimatedItemHeight: estimateItemHeight, keyExtractor: getItemKey, initialScrollIndex: SCROLL_TO_ITEM_END, initialScrollOffsetInIndex: SCROLL_TO_ITEM_END }) }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [_jsx(StatusBar, { agent: agent, isProcessing: ui.isProcessing, isThinking: ui.isThinking, isCompacting: ui.isCompacting, approvalQueueCount: approvalQueue.length, copyModeEnabled: ui.copyModeEnabled, isAwaitingApproval: approval !== null, todoExpanded: ui.todoExpanded, hasTodos: todos.some((t) => t.status !== 'completed'), planModeActive: ui.planModeActive, autoApproveEdits: ui.autoApproveEdits, backgroundTasksRunning: ui.backgroundTasksRunning }), _jsx(BackgroundTasksPanel, { tasks: ui.backgroundTasks, isExpanded: ui.backgroundTasksExpanded, isProcessing: ui.isProcessing }), _jsx(TodoPanel, { todos: todos, isExpanded: ui.todoExpanded, isProcessing: ui.isProcessing }), selectionHintVisible && (_jsx(Box, { paddingX: 1, children: _jsx(Text, { color: "yellowBright", children: "\uD83D\uDCA1 Tip: Hold Option (\u2325) and click to select text, or press Ctrl+S to toggle copy mode" }) })), _jsx(QueuedMessagesDisplay, { messages: queuedMessages }), _jsx(InputContainer, { ref: inputContainerRef, buffer: buffer, input: input, ui: ui, session: session, initialPrompt: initialPrompt, approval: approval, queuedMessages: queuedMessages, setInput: setInput, setUi: setUi, setSession: setSession, setMessages: setMessages, setPendingMessages: setPendingMessages, setDequeuedBuffer: setDequeuedBuffer, setQueuedMessages: setQueuedMessages, setApproval: setApproval, setApprovalQueue: setApprovalQueue, setTodos: setTodos, agent: agent, inputService: inputService, onKeyboardScroll: handleKeyboardScroll, useStreaming: useStreaming, configFilePath: configFilePath }), _jsx(OverlayContainer, { ref: overlayContainerRef, ui: ui, input: input, session: session, approval: approval, setInput: setInput, setUi: setUi, setSession: setSession, setMessages: setMessages, setApproval: setApproval, setApprovalQueue: setApprovalQueue, agent: agent, inputService: inputService, buffer: buffer, onSubmitPromptCommand: handleSubmitPromptCommand, configFilePath: configFilePath }), ui.exitWarningShown && (_jsxs(Box, { paddingX: 1, children: [_jsx(Text, { color: "yellowBright", bold: true, children: "\u26A0 Press Ctrl+C again to exit" }), _jsx(Text, { color: "gray", children: " (or press any key to cancel)" })] })), _jsx(Footer, { agent: agent, sessionId: session.id, modelName: session.modelName, cwd: process.cwd(), ...(branchName ? { branchName } : {}), autoApproveEdits: ui.autoApproveEdits, planModeActive: ui.planModeActive, isShellMode: buffer.text.startsWith('!') }), ui.historySearch.isActive && (_jsx(HistorySearchBar, { query: ui.historySearch.query, hasMatch: historySearchHasMatch }))] })] }));
180
+ const hideChrome = shouldHideStatusChrome(ui.activeOverlay, approval);
181
+ return (_jsxs(Box, { flexDirection: "column", height: terminalHeight, children: [_jsx(Box, { ref: listContainerRef, flexGrow: 1, flexShrink: 1, minHeight: 0, children: _jsx(VirtualizedList, { ref: listRef, data: listData, renderItem: renderListItem, estimatedItemHeight: estimateItemHeight, keyExtractor: getItemKey, initialScrollIndex: SCROLL_TO_ITEM_END, initialScrollOffsetInIndex: SCROLL_TO_ITEM_END }) }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [!hideChrome && (_jsxs(_Fragment, { children: [_jsx(StatusBar, { agent: agent, isProcessing: ui.isProcessing, isThinking: ui.isThinking, isCompacting: ui.isCompacting, approvalQueueCount: approvalQueue.length, copyModeEnabled: ui.copyModeEnabled, isAwaitingApproval: approval !== null, todoExpanded: ui.todoExpanded, hasTodos: todos.some((t) => t.status !== 'completed'), planModeActive: ui.planModeActive, autoApproveEdits: ui.autoApproveEdits, backgroundTasksRunning: ui.backgroundTasksRunning }), _jsx(BackgroundTasksPanel, { tasks: ui.backgroundTasks, isExpanded: ui.backgroundTasksExpanded, isProcessing: ui.isProcessing }), _jsx(TodoPanel, { todos: todos, isExpanded: ui.todoExpanded, isProcessing: ui.isProcessing }), selectionHintVisible && (_jsx(Box, { paddingX: 1, children: _jsx(Text, { color: "yellowBright", children: "\uD83D\uDCA1 Tip: Hold Option (\u2325) and click to select text, or press Ctrl+S to toggle copy mode" }) })), _jsx(QueuedMessagesDisplay, { messages: queuedMessages })] })), _jsx(InputContainer, { ref: inputContainerRef, buffer: buffer, input: input, ui: ui, session: session, initialPrompt: initialPrompt, approval: approval, queuedMessages: queuedMessages, setInput: setInput, setUi: setUi, setSession: setSession, setMessages: setMessages, setPendingMessages: setPendingMessages, setDequeuedBuffer: setDequeuedBuffer, setQueuedMessages: setQueuedMessages, setApproval: setApproval, setApprovalQueue: setApprovalQueue, setTodos: setTodos, agent: agent, inputService: inputService, onKeyboardScroll: handleKeyboardScroll, useStreaming: useStreaming, configFilePath: configFilePath }), _jsx(OverlayContainer, { ref: overlayContainerRef, ui: ui, input: input, session: session, approval: approval, setInput: setInput, setUi: setUi, setSession: setSession, setMessages: setMessages, setApproval: setApproval, setApprovalQueue: setApprovalQueue, agent: agent, inputService: inputService, buffer: buffer, onSubmitPromptCommand: handleSubmitPromptCommand, configFilePath: configFilePath }), !hideChrome && ui.exitWarningShown && (_jsxs(Box, { paddingX: 1, children: [_jsx(Text, { color: "yellowBright", bold: true, children: "\u26A0 Press Ctrl+C again to exit" }), _jsx(Text, { color: "gray", children: " (or press any key to cancel)" })] })), !hideChrome && (_jsx(Footer, { agent: agent, sessionId: session.id, modelName: session.modelName, cwd: process.cwd(), ...(branchName ? { branchName } : {}), autoApproveEdits: ui.autoApproveEdits, planModeActive: ui.planModeActive, isShellMode: buffer.text.startsWith('!') })), !hideChrome && ui.historySearch.isActive && (_jsx(HistorySearchBar, { query: ui.historySearch.query, hasMatch: historySearchHasMatch }))] })] }));
180
182
  }
@@ -1 +1 @@
1
- {"version":3,"file":"StaticCLI.d.ts","sourceRoot":"","sources":["../../../../../src/cli/ink-cli/components/modes/StaticCLI.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAM9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAqBxD,UAAU,cAAc;IACpB,KAAK,EAAE,UAAU,CAAC;IAClB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,WAAW,EAAE,WAAW,CAAC;IACzB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,6DAA6D;IAC7D,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,wBAAgB,SAAS,CAAC,EACtB,KAAK,EACL,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,cAAc,EACd,YAAmB,GACtB,EAAE,cAAc,2CAsPhB"}
1
+ {"version":3,"file":"StaticCLI.d.ts","sourceRoot":"","sources":["../../../../../src/cli/ink-cli/components/modes/StaticCLI.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAM9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAsBxD,UAAU,cAAc;IACpB,KAAK,EAAE,UAAU,CAAC;IAClB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,WAAW,EAAE,WAAW,CAAC;IACzB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,6DAA6D;IAC7D,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,wBAAgB,SAAS,CAAC,EACtB,KAAK,EACL,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,cAAc,EACd,YAAmB,GACtB,EAAE,cAAc,2CA+PhB"}
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /**
3
3
  * StaticCLI - Static pattern rendering mode
4
4
  *
@@ -34,6 +34,7 @@ import { BackgroundTasksPanel } from '../BackgroundTasksPanel.js';
34
34
  // Containers
35
35
  import { InputContainer } from '../../containers/InputContainer.js';
36
36
  import { OverlayContainer } from '../../containers/OverlayContainer.js';
37
+ import { shouldHideStatusChrome } from '../../utils/overlayPresentation.js';
37
38
  export function StaticCLI({ agent, initialSessionId, initialPrompt, startupInfo, configFilePath, useStreaming = true, }) {
38
39
  // Use shared CLI state (no keyboard scroll in Static mode)
39
40
  const { messages, setMessages, pendingMessages, setPendingMessages, dequeuedBuffer, setDequeuedBuffer, queuedMessages, setQueuedMessages, todos, setTodos, ui, setUi, input, setInput, session, setSession, approval, setApproval, approvalQueue, setApprovalQueue, inputService, buffer, overlayContainerRef, visibleMessages, } = useCLIState({
@@ -103,5 +104,11 @@ export function StaticCLI({ agent, initialSessionId, initialPrompt, startupInfo,
103
104
  startupInfo,
104
105
  terminalWidth,
105
106
  ]);
106
- return (_jsxs(Box, { flexDirection: "column", width: terminalWidth, children: [_jsx(Static, { items: staticItems, children: (item) => item }, staticRemountKey), pendingMessages.map((message) => (_jsx(MessageItem, { message: message, terminalWidth: terminalWidth }, message.id))), dequeuedBuffer.map((message) => (_jsx(MessageItem, { message: message, terminalWidth: terminalWidth }, message.id))), _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [_jsx(StatusBar, { agent: agent, isProcessing: ui.isProcessing, isThinking: ui.isThinking, isCompacting: ui.isCompacting, approvalQueueCount: approvalQueue.length, copyModeEnabled: ui.copyModeEnabled, isAwaitingApproval: approval !== null, todoExpanded: ui.todoExpanded, hasTodos: todos.some((t) => t.status !== 'completed'), planModeActive: ui.planModeActive, autoApproveEdits: ui.autoApproveEdits, backgroundTasksRunning: ui.backgroundTasksRunning }), _jsx(BackgroundTasksPanel, { tasks: ui.backgroundTasks, isExpanded: ui.backgroundTasksExpanded, isProcessing: ui.isProcessing }), _jsx(TodoPanel, { todos: todos, isExpanded: ui.todoExpanded, isProcessing: ui.isProcessing }), _jsx(QueuedMessagesDisplay, { messages: queuedMessages }), _jsx(InputContainer, { ref: inputContainerRef, buffer: buffer, input: input, ui: ui, session: session, initialPrompt: initialPrompt, approval: approval, queuedMessages: queuedMessages, setInput: setInput, setUi: setUi, setSession: setSession, setMessages: setMessages, setPendingMessages: setPendingMessages, setDequeuedBuffer: setDequeuedBuffer, setQueuedMessages: setQueuedMessages, setApproval: setApproval, setApprovalQueue: setApprovalQueue, setTodos: setTodos, agent: agent, inputService: inputService, useStreaming: useStreaming, configFilePath: configFilePath }), _jsx(OverlayContainer, { ref: overlayContainerRef, ui: ui, input: input, session: session, approval: approval, setInput: setInput, setUi: setUi, setSession: setSession, setMessages: setMessages, setApproval: setApproval, setApprovalQueue: setApprovalQueue, agent: agent, inputService: inputService, buffer: buffer, refreshStatic: refreshStatic, onSubmitPromptCommand: handleSubmitPromptCommand, configFilePath: configFilePath }), ui.exitWarningShown && (_jsxs(Box, { paddingX: 1, children: [_jsx(Text, { color: "yellowBright", bold: true, children: "\u26A0 Press Ctrl+C again to exit" }), _jsx(Text, { color: "gray", children: " (or press any key to cancel)" })] })), _jsx(Footer, { agent: agent, sessionId: session.id, modelName: session.modelName, cwd: process.cwd(), ...(branchName ? { branchName } : {}), autoApproveEdits: ui.autoApproveEdits, planModeActive: ui.planModeActive, isShellMode: buffer.text.startsWith('!') }), ui.historySearch.isActive && (_jsx(HistorySearchBar, { query: ui.historySearch.query, hasMatch: historySearchHasMatch }))] })] }));
107
+ return (_jsxs(Box, { flexDirection: "column", width: terminalWidth, children: [_jsx(Static, { items: staticItems, children: (item) => item }, staticRemountKey), pendingMessages.map((message) => (_jsx(MessageItem, { message: message, terminalWidth: terminalWidth }, message.id))), dequeuedBuffer.map((message) => (_jsx(MessageItem, { message: message, terminalWidth: terminalWidth }, message.id))), _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [(() => {
108
+ const hideChrome = shouldHideStatusChrome(ui.activeOverlay, approval);
109
+ if (hideChrome)
110
+ return null;
111
+ return (_jsxs(_Fragment, { children: [_jsx(StatusBar, { agent: agent, isProcessing: ui.isProcessing, isThinking: ui.isThinking, isCompacting: ui.isCompacting, approvalQueueCount: approvalQueue.length, copyModeEnabled: ui.copyModeEnabled, isAwaitingApproval: approval !== null, todoExpanded: ui.todoExpanded, hasTodos: todos.some((t) => t.status !== 'completed'), planModeActive: ui.planModeActive, autoApproveEdits: ui.autoApproveEdits, backgroundTasksRunning: ui.backgroundTasksRunning }), _jsx(BackgroundTasksPanel, { tasks: ui.backgroundTasks, isExpanded: ui.backgroundTasksExpanded, isProcessing: ui.isProcessing }), _jsx(TodoPanel, { todos: todos, isExpanded: ui.todoExpanded, isProcessing: ui.isProcessing }), _jsx(QueuedMessagesDisplay, { messages: queuedMessages })] }));
112
+ })(), _jsx(InputContainer, { ref: inputContainerRef, buffer: buffer, input: input, ui: ui, session: session, initialPrompt: initialPrompt, approval: approval, queuedMessages: queuedMessages, setInput: setInput, setUi: setUi, setSession: setSession, setMessages: setMessages, setPendingMessages: setPendingMessages, setDequeuedBuffer: setDequeuedBuffer, setQueuedMessages: setQueuedMessages, setApproval: setApproval, setApprovalQueue: setApprovalQueue, setTodos: setTodos, agent: agent, inputService: inputService, useStreaming: useStreaming, configFilePath: configFilePath }), _jsx(OverlayContainer, { ref: overlayContainerRef, ui: ui, input: input, session: session, approval: approval, setInput: setInput, setUi: setUi, setSession: setSession, setMessages: setMessages, setApproval: setApproval, setApprovalQueue: setApprovalQueue, agent: agent, inputService: inputService, buffer: buffer, refreshStatic: refreshStatic, onSubmitPromptCommand: handleSubmitPromptCommand, configFilePath: configFilePath }), !shouldHideStatusChrome(ui.activeOverlay, approval) && ui.exitWarningShown && (_jsxs(Box, { paddingX: 1, children: [_jsx(Text, { color: "yellowBright", bold: true, children: "\u26A0 Press Ctrl+C again to exit" }), _jsx(Text, { color: "gray", children: " (or press any key to cancel)" })] })), !shouldHideStatusChrome(ui.activeOverlay, approval) && (_jsx(Footer, { agent: agent, sessionId: session.id, modelName: session.modelName, cwd: process.cwd(), ...(branchName ? { branchName } : {}), autoApproveEdits: ui.autoApproveEdits, planModeActive: ui.planModeActive, isShellMode: buffer.text.startsWith('!') })), !shouldHideStatusChrome(ui.activeOverlay, approval) &&
113
+ ui.historySearch.isActive && (_jsx(HistorySearchBar, { query: ui.historySearch.query, hasMatch: historySearchHasMatch }))] })] }));
107
114
  }
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import type { Key } from '../../hooks/useInputOrchestrator.js';
3
+ export interface CommandOutputOverlayProps {
4
+ isVisible: boolean;
5
+ title: string;
6
+ content: string;
7
+ onClose: () => void;
8
+ }
9
+ export interface CommandOutputOverlayHandle {
10
+ handleInput: (input: string, key: Key) => boolean;
11
+ }
12
+ export declare const CommandOutputOverlay: React.ForwardRefExoticComponent<CommandOutputOverlayProps & React.RefAttributes<CommandOutputOverlayHandle>>;
13
+ //# sourceMappingURL=CommandOutputOverlay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CommandOutputOverlay.d.ts","sourceRoot":"","sources":["../../../../../src/cli/ink-cli/components/overlays/CommandOutputOverlay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAwE,MAAM,OAAO,CAAC;AAG7F,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,qCAAqC,CAAC;AAK/D,MAAM,WAAW,yBAAyB;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,0BAA0B;IACvC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;CACrD;AAED,eAAO,MAAM,oBAAoB,8GAuF/B,CAAC"}
@@ -0,0 +1,60 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react';
3
+ import { Box, Text } from 'ink';
4
+ import wrapAnsi from 'wrap-ansi';
5
+ import { useTerminalSize } from '../../hooks/useTerminalSize.js';
6
+ import { getMaxVisibleItemsForTerminalRows } from '../../utils/overlaySizing.js';
7
+ import { HintBar } from '../shared/HintBar.js';
8
+ export const CommandOutputOverlay = forwardRef(function CommandOutputOverlay({ isVisible, title, content, onClose }, ref) {
9
+ const { columns, rows } = useTerminalSize();
10
+ const viewportLines = useMemo(() => {
11
+ return getMaxVisibleItemsForTerminalRows({
12
+ rows,
13
+ hardCap: 20,
14
+ reservedRows: 10,
15
+ });
16
+ }, [rows]);
17
+ const [scrollOffset, setScrollOffset] = useState(0);
18
+ const wrappedLines = useMemo(() => {
19
+ const width = Math.max(20, columns - 2);
20
+ const wrapped = wrapAnsi(content || '', width, {
21
+ hard: true,
22
+ wordWrap: true,
23
+ trim: false,
24
+ });
25
+ return wrapped.length > 0 ? wrapped.split('\n') : [];
26
+ }, [content, columns]);
27
+ const maxScrollOffset = useMemo(() => {
28
+ return Math.max(0, wrappedLines.length - viewportLines);
29
+ }, [wrappedLines.length, viewportLines]);
30
+ useEffect(() => {
31
+ setScrollOffset((prev) => Math.min(maxScrollOffset, Math.max(0, prev)));
32
+ }, [maxScrollOffset]);
33
+ useImperativeHandle(ref, () => ({
34
+ handleInput: (_input, key) => {
35
+ if (!isVisible)
36
+ return false;
37
+ if (key.escape) {
38
+ onClose();
39
+ return true;
40
+ }
41
+ if (key.upArrow) {
42
+ setScrollOffset((prev) => Math.max(0, prev - 1));
43
+ return true;
44
+ }
45
+ if (key.downArrow) {
46
+ setScrollOffset((prev) => Math.min(maxScrollOffset, prev + 1));
47
+ return true;
48
+ }
49
+ return true;
50
+ },
51
+ }), [isVisible, maxScrollOffset, onClose]);
52
+ if (!isVisible)
53
+ return null;
54
+ const visible = wrappedLines.slice(scrollOffset, scrollOffset + viewportLines);
55
+ const hint = wrappedLines.length > viewportLines ? ['↑↓ scroll', 'Esc close'] : ['Esc close'];
56
+ return (_jsxs(Box, { flexDirection: "column", width: columns, children: [_jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "cyan", bold: true, wrap: "truncate-end", children: title }) }), _jsx(Box, { flexDirection: "column", height: viewportLines, marginTop: 1, children: Array.from({ length: viewportLines }, (_, rowIndex) => {
57
+ const line = visible[rowIndex];
58
+ return (_jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { wrap: "truncate-end", children: line ?? ' ' }) }, `line-${rowIndex}`));
59
+ }) }), _jsx(Box, { paddingX: 0, paddingY: 0, marginTop: 1, children: _jsx(HintBar, { hints: hint }) })] }));
60
+ });
@@ -65,6 +65,6 @@ const LogLevelSelector = forwardRef(function LogLevelSelector({ isVisible, onSel
65
65
  const handleSelect = (option) => {
66
66
  onSelect(option.level);
67
67
  };
68
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(BaseSelector, { ref: baseSelectorRef, items: levels, isVisible: isVisible, isLoading: false, selectedIndex: selectedIndex, onSelectIndex: setSelectedIndex, onSelect: handleSelect, onClose: onClose, formatItem: formatItem, title: "Select Log Level", borderColor: "yellowBright", emptyMessage: "No log levels available" }), logFilePath && process.env.DEXTO_DEV_MODE === 'true' && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "gray", children: ["\uD83D\uDCC1 Log file: ", logFilePath] }) }))] }));
68
+ return (_jsxs(Box, { flexDirection: "column", children: [logFilePath && (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: "gray", children: ["\uD83D\uDCC1 Log file: ", logFilePath] }) })), _jsx(BaseSelector, { ref: baseSelectorRef, items: levels, isVisible: isVisible, isLoading: false, selectedIndex: selectedIndex, onSelectIndex: setSelectedIndex, onSelect: handleSelect, onClose: onClose, formatItem: formatItem, title: "Select Log Level", borderColor: "yellowBright", emptyMessage: "No log levels available" })] }));
69
69
  });
70
70
  export default LogLevelSelector;
@@ -1 +1 @@
1
- {"version":3,"file":"ModelSelectorRefactored.d.ts","sourceRoot":"","sources":["../../../../../src/cli/ink-cli/components/overlays/ModelSelectorRefactored.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,qCAAqC,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAO3D,OAAO,EAMH,KAAK,WAAW,EACnB,MAAM,yBAAyB,CAAC;AAGjC,KAAK,eAAe,GAAG,MAAM,GAAG,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAEhF,UAAU,kBAAkB;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,CACX,QAAQ,EAAE,WAAW,EACrB,KAAK,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,MAAM,EAChB,eAAe,CAAC,EAAE,eAAe,KAChC,IAAI,CAAC;IACV,iBAAiB,EAAE,CACf,QAAQ,EAAE,WAAW,EACrB,KAAK,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,MAAM,EAChB,eAAe,CAAC,EAAE,eAAe,KAChC,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,iBAAiB,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IAChD,KAAK,EAAE,UAAU,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAChC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;CACrD;AAoDD;;GAEG;AACH,QAAA,MAAM,aAAa,oHAq3BjB,CAAC;AAEH,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"ModelSelectorRefactored.d.ts","sourceRoot":"","sources":["../../../../../src/cli/ink-cli/components/overlays/ModelSelectorRefactored.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,qCAAqC,CAAC;AAE/D,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAO3D,OAAO,EAMH,KAAK,WAAW,EACnB,MAAM,yBAAyB,CAAC;AAKjC,KAAK,eAAe,GAAG,MAAM,GAAG,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAEhF,UAAU,kBAAkB;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,CACX,QAAQ,EAAE,WAAW,EACrB,KAAK,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,MAAM,EAChB,eAAe,CAAC,EAAE,eAAe,KAChC,IAAI,CAAC;IACV,iBAAiB,EAAE,CACf,QAAQ,EAAE,WAAW,EACrB,KAAK,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,MAAM,EAChB,eAAe,CAAC,EAAE,eAAe,KAChC,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,iBAAiB,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IAChD,KAAK,EAAE,UAAU,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAChC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;CACrD;AA8FD;;GAEG;AACH,QAAA,MAAM,aAAa,oHAo8BjB,CAAC;AAEH,eAAe,aAAa,CAAC"}
@@ -7,13 +7,38 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
7
7
  */
8
8
  import { useState, useEffect, forwardRef, useRef, useImperativeHandle, useMemo, useCallback, } from 'react';
9
9
  import { Box, Text } from 'ink';
10
+ import { useTerminalSize } from '../../hooks/useTerminalSize.js';
10
11
  import { listOllamaModels, DEFAULT_OLLAMA_URL, getLocalModelById, isReasoningCapableModel, } from '@dexto/core';
11
12
  import { loadCustomModels, deleteCustomModel, getAllInstalledModels, loadGlobalPreferences, isDextoAuthEnabled, } from '@dexto/agent-management';
12
13
  import { getLLMProviderDisplayName } from '../../utils/llm-provider-display.js';
14
+ import { getMaxVisibleItemsForTerminalRows } from '../../utils/overlaySizing.js';
15
+ import { HintBar } from '../shared/HintBar.js';
13
16
  function isAddCustomOption(item) {
14
17
  return 'type' in item && item.type === 'add-custom';
15
18
  }
16
- const MAX_VISIBLE_ITEMS = 10;
19
+ function asModelOption(item) {
20
+ return item;
21
+ }
22
+ function getRowPrefix({ isSelected, isDefault, isCurrent, isCustom, }) {
23
+ return `${isSelected ? '›' : ' '} ${isDefault ? '✓' : ' '} ${isCurrent ? '●' : ' '} ${isCustom ? '★' : ' '}`;
24
+ }
25
+ function computeNextSelection(currentIndex, itemsLength, viewportItems) {
26
+ const nextIndex = currentIndex;
27
+ let nextOffset = 0;
28
+ const modelsLength = Math.max(0, itemsLength - 1);
29
+ if (nextIndex > 0) {
30
+ const modelIndex = nextIndex - 1;
31
+ if (modelIndex < nextOffset) {
32
+ nextOffset = modelIndex;
33
+ }
34
+ else if (modelIndex >= nextOffset + viewportItems) {
35
+ nextOffset = Math.max(0, modelIndex - viewportItems + 1);
36
+ }
37
+ }
38
+ const maxOffset = Math.max(0, modelsLength - viewportItems);
39
+ nextOffset = Math.min(maxOffset, Math.max(0, nextOffset));
40
+ return { index: nextIndex, offset: nextOffset };
41
+ }
17
42
  // Reasoning effort options - defined at module scope to avoid recreation on each render
18
43
  const REASONING_EFFORT_OPTIONS = [
19
44
  {
@@ -36,23 +61,33 @@ const REASONING_EFFORT_OPTIONS = [
36
61
  * Model selector with search and custom model support
37
62
  */
38
63
  const ModelSelector = forwardRef(function ModelSelector({ isVisible, onSelectModel, onSetDefaultModel, onClose, onAddCustomModel, onEditCustomModel, agent, }, ref) {
64
+ const { rows: terminalRows } = useTerminalSize();
65
+ const maxVisibleItems = useMemo(() => {
66
+ return getMaxVisibleItemsForTerminalRows({
67
+ rows: terminalRows,
68
+ hardCap: 8,
69
+ reservedRows: 14,
70
+ });
71
+ }, [terminalRows]);
39
72
  const [models, setModels] = useState([]);
40
73
  const [customModels, setCustomModels] = useState([]);
41
74
  const [isLoading, setIsLoading] = useState(false);
42
- const [selectedIndex, setSelectedIndex] = useState(0);
75
+ const [selection, setSelection] = useState({ index: 0, offset: 0 });
43
76
  const [searchQuery, setSearchQuery] = useState('');
44
- const [scrollOffset, setScrollOffset] = useState(0);
45
77
  const [customModelAction, setCustomModelAction] = useState(null);
46
78
  const [pendingDeleteConfirm, setPendingDeleteConfirm] = useState(false);
47
- const selectedIndexRef = useRef(selectedIndex);
79
+ const selectedIndexRef = useRef(0);
48
80
  const deleteTimeoutRef = useRef(null);
81
+ const maxVisibleItemsRef = useRef(maxVisibleItems);
49
82
  // Reasoning effort sub-step state
50
83
  const [pendingReasoningModel, setPendingReasoningModel] = useState(null);
51
84
  const [reasoningEffortIndex, setReasoningEffortIndex] = useState(0); // Default to 'Auto' (index 0)
52
85
  const [isSettingDefault, setIsSettingDefault] = useState(false); // Track if setting as default vs normal selection
53
86
  const [refreshVersion, setRefreshVersion] = useState(0);
54
87
  // Keep ref in sync
55
- selectedIndexRef.current = selectedIndex;
88
+ selectedIndexRef.current = selection.index;
89
+ maxVisibleItemsRef.current = maxVisibleItems;
90
+ const modelsViewportItems = Math.max(1, maxVisibleItems - 1);
56
91
  // Clear delete confirmation timeout on unmount
57
92
  useEffect(() => {
58
93
  return () => {
@@ -67,9 +102,10 @@ const ModelSelector = forwardRef(function ModelSelector({ isVisible, onSelectMod
67
102
  return;
68
103
  let cancelled = false;
69
104
  setIsLoading(true);
105
+ setModels([]);
106
+ setCustomModels([]);
70
107
  setSearchQuery('');
71
- setSelectedIndex(0);
72
- setScrollOffset(0);
108
+ setSelection({ index: 0, offset: 0 });
73
109
  setCustomModelAction(null);
74
110
  setPendingDeleteConfirm(false);
75
111
  setPendingReasoningModel(null);
@@ -237,7 +273,13 @@ const ModelSelector = forwardRef(function ModelSelector({ isVisible, onSelectMod
237
273
  // Set initial selection to current model (offset by 1 for "Add custom" option)
238
274
  const currentIndex = modelList.findIndex((m) => m.isCurrent);
239
275
  if (currentIndex >= 0) {
240
- setSelectedIndex(currentIndex + 1); // +1 for "Add custom" at top
276
+ const nextIndex = currentIndex + 1; // +1 for "Add custom" at top
277
+ const nextMaxVisibleItems = maxVisibleItemsRef.current;
278
+ const nextModelsViewportItems = Math.max(1, nextMaxVisibleItems - 1);
279
+ const maxOffset = Math.max(0, modelList.length - nextModelsViewportItems);
280
+ const nextOffset = Math.min(maxOffset, Math.max(0, currentIndex - nextModelsViewportItems + 1));
281
+ selectedIndexRef.current = nextIndex;
282
+ setSelection({ index: nextIndex, offset: nextOffset });
241
283
  }
242
284
  }
243
285
  }
@@ -260,31 +302,41 @@ const ModelSelector = forwardRef(function ModelSelector({ isVisible, onSelectMod
260
302
  if (!searchQuery.trim()) {
261
303
  return [addCustomOption, ...models];
262
304
  }
263
- const query = searchQuery.toLowerCase();
305
+ const query = searchQuery.toLowerCase().replace(/[\s-]+/g, '');
264
306
  const filtered = models.filter((model) => {
265
- const name = model.name.toLowerCase();
266
- const displayName = (model.displayName || '').toLowerCase();
267
- const provider = model.provider.toLowerCase();
307
+ const name = model.name.toLowerCase().replace(/[\s-]+/g, '');
308
+ const displayName = (model.displayName || '').toLowerCase().replace(/[\s-]+/g, '');
309
+ const provider = model.provider.toLowerCase().replace(/[\s-]+/g, '');
268
310
  return name.includes(query) || displayName.includes(query) || provider.includes(query);
269
311
  });
270
312
  // Always show "Add custom" when searching (user might want to add what they're searching for)
271
313
  return [addCustomOption, ...filtered];
272
314
  }, [models, searchQuery]);
273
- // Adjust selected index when filter changes
274
- useEffect(() => {
275
- if (selectedIndex >= filteredItems.length) {
276
- setSelectedIndex(Math.max(0, filteredItems.length - 1));
277
- }
278
- }, [filteredItems.length, selectedIndex]);
279
- // Calculate scroll offset
315
+ // Keep selection valid and visible when filtering or terminal height changes.
280
316
  useEffect(() => {
281
- if (selectedIndex < scrollOffset) {
282
- setScrollOffset(selectedIndex);
283
- }
284
- else if (selectedIndex >= scrollOffset + MAX_VISIBLE_ITEMS) {
285
- setScrollOffset(selectedIndex - MAX_VISIBLE_ITEMS + 1);
286
- }
287
- }, [selectedIndex, scrollOffset]);
317
+ setSelection((prev) => {
318
+ const maxIndex = Math.max(0, filteredItems.length - 1);
319
+ const nextIndex = Math.min(prev.index, maxIndex);
320
+ let nextOffset = prev.offset;
321
+ const nextModelsLength = Math.max(0, filteredItems.length - 1);
322
+ if (nextIndex > 0) {
323
+ const modelIndex = nextIndex - 1;
324
+ if (modelIndex < nextOffset) {
325
+ nextOffset = modelIndex;
326
+ }
327
+ else if (modelIndex >= nextOffset + modelsViewportItems) {
328
+ nextOffset = Math.max(0, modelIndex - modelsViewportItems + 1);
329
+ }
330
+ }
331
+ const maxOffset = Math.max(0, nextModelsLength - modelsViewportItems);
332
+ nextOffset = Math.min(maxOffset, Math.max(0, nextOffset));
333
+ if (nextIndex === prev.index && nextOffset === prev.offset) {
334
+ return prev;
335
+ }
336
+ selectedIndexRef.current = nextIndex;
337
+ return { index: nextIndex, offset: nextOffset };
338
+ });
339
+ }, [filteredItems.length, modelsViewportItems]);
288
340
  // Handle delete custom model
289
341
  const handleDeleteCustomModel = useCallback(async (model) => {
290
342
  if (!model.isCustom)
@@ -315,6 +367,13 @@ const ModelSelector = forwardRef(function ModelSelector({ isVisible, onSelectMod
315
367
  handleInput: (input, key) => {
316
368
  if (!isVisible)
317
369
  return false;
370
+ // While loading, allow closing but ignore all other input.
371
+ if (isLoading) {
372
+ if (key.escape) {
373
+ onClose();
374
+ }
375
+ return true;
376
+ }
318
377
  // Handle reasoning effort sub-step
319
378
  if (pendingReasoningModel) {
320
379
  if (key.escape) {
@@ -391,41 +450,10 @@ const ModelSelector = forwardRef(function ModelSelector({ isVisible, onSelectMod
391
450
  setPendingDeleteConfirm(false);
392
451
  return true;
393
452
  }
394
- const actionItem = currentItem;
395
- // Check if reasoning-capable, show reasoning effort selection
396
- if (isReasoningCapableModel(actionItem.name)) {
397
- setPendingReasoningModel(actionItem);
398
- setIsSettingDefault(true);
399
- setReasoningEffortIndex(0); // Default to 'Auto'
400
- return true;
401
- }
402
- clearActionState();
403
- void (async () => {
404
- await onSetDefaultModel(actionItem.provider, actionItem.name, actionItem.displayName, actionItem.baseURL, actionItem.reasoningEffort);
405
- setRefreshVersion((prev) => prev + 1);
406
- onClose(); // Close overlay after setting default
407
- })();
408
453
  return true;
409
454
  }
410
455
  if (customModelAction === 'delete') {
411
- if (pendingDeleteConfirm) {
412
- if (deleteTimeoutRef.current) {
413
- clearTimeout(deleteTimeoutRef.current);
414
- deleteTimeoutRef.current = null;
415
- }
416
- clearActionState();
417
- void handleDeleteCustomModel(currentItem);
418
- }
419
- else {
420
- setPendingDeleteConfirm(true);
421
- if (deleteTimeoutRef.current) {
422
- clearTimeout(deleteTimeoutRef.current);
423
- }
424
- deleteTimeoutRef.current = setTimeout(() => {
425
- setPendingDeleteConfirm(false);
426
- deleteTimeoutRef.current = null;
427
- }, 3000);
428
- }
456
+ // Use Enter for delete confirmation/execution to avoid accidental deletes.
429
457
  return true;
430
458
  }
431
459
  }
@@ -469,8 +497,8 @@ const ModelSelector = forwardRef(function ModelSelector({ isVisible, onSelectMod
469
497
  // Regular character - add to search
470
498
  if (input.length === 1 && input.charCodeAt(0) >= 32) {
471
499
  setSearchQuery((prev) => prev + input);
472
- setSelectedIndex(0);
473
- setScrollOffset(0);
500
+ selectedIndexRef.current = 0;
501
+ setSelection({ index: 0, offset: 0 });
474
502
  return true;
475
503
  }
476
504
  }
@@ -487,8 +515,23 @@ const ModelSelector = forwardRef(function ModelSelector({ isVisible, onSelectMod
487
515
  clearActionState();
488
516
  }
489
517
  const nextIndex = (selectedIndexRef.current - 1 + itemsLength) % itemsLength;
490
- setSelectedIndex(nextIndex);
491
518
  selectedIndexRef.current = nextIndex;
519
+ setSelection((prev) => {
520
+ let nextOffset = prev.offset;
521
+ const nextModelsLength = Math.max(0, itemsLength - 1);
522
+ if (nextIndex > 0) {
523
+ const modelIndex = nextIndex - 1;
524
+ if (modelIndex < prev.offset) {
525
+ nextOffset = modelIndex;
526
+ }
527
+ else if (modelIndex >= prev.offset + modelsViewportItems) {
528
+ nextOffset = Math.max(0, modelIndex - modelsViewportItems + 1);
529
+ }
530
+ }
531
+ const maxOffset = Math.max(0, nextModelsLength - modelsViewportItems);
532
+ nextOffset = Math.min(maxOffset, Math.max(0, nextOffset));
533
+ return { index: nextIndex, offset: nextOffset };
534
+ });
492
535
  return true;
493
536
  }
494
537
  if (key.downArrow) {
@@ -497,8 +540,23 @@ const ModelSelector = forwardRef(function ModelSelector({ isVisible, onSelectMod
497
540
  clearActionState();
498
541
  }
499
542
  const nextIndex = (selectedIndexRef.current + 1) % itemsLength;
500
- setSelectedIndex(nextIndex);
501
543
  selectedIndexRef.current = nextIndex;
544
+ setSelection((prev) => {
545
+ let nextOffset = prev.offset;
546
+ const nextModelsLength = Math.max(0, itemsLength - 1);
547
+ if (nextIndex > 0) {
548
+ const modelIndex = nextIndex - 1;
549
+ if (modelIndex < prev.offset) {
550
+ nextOffset = modelIndex;
551
+ }
552
+ else if (modelIndex >= prev.offset + modelsViewportItems) {
553
+ nextOffset = Math.max(0, modelIndex - modelsViewportItems + 1);
554
+ }
555
+ }
556
+ const maxOffset = Math.max(0, nextModelsLength - modelsViewportItems);
557
+ nextOffset = Math.min(maxOffset, Math.max(0, nextOffset));
558
+ return { index: nextIndex, offset: nextOffset };
559
+ });
502
560
  return true;
503
561
  }
504
562
  if (key.return && itemsLength > 0) {
@@ -568,7 +626,10 @@ const ModelSelector = forwardRef(function ModelSelector({ isVisible, onSelectMod
568
626
  },
569
627
  }), [
570
628
  isVisible,
629
+ isLoading,
571
630
  filteredItems,
631
+ maxVisibleItems,
632
+ modelsViewportItems,
572
633
  onClose,
573
634
  onSelectModel,
574
635
  onSetDefaultModel,
@@ -584,47 +645,99 @@ const ModelSelector = forwardRef(function ModelSelector({ isVisible, onSelectMod
584
645
  ]);
585
646
  if (!isVisible)
586
647
  return null;
587
- if (isLoading) {
588
- return (_jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "gray", children: "Loading models..." }) }));
589
- }
590
648
  // Reasoning effort sub-step UI
591
649
  if (pendingReasoningModel) {
592
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { paddingX: 0, paddingY: 0, children: _jsxs(Text, { color: "cyan", bold: true, children: ["Configure Reasoning Effort", isSettingDefault && _jsx(Text, { color: "gray", children: " (Setting as Default)" })] }) }), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsxs(Text, { color: "gray", children: ["for ", pendingReasoningModel.displayName || pendingReasoningModel.name] }) }), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "gray", children: "\u2191\u2193 navigate, Enter select, Esc back" }) }), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "gray", children: '─'.repeat(50) }) }), REASONING_EFFORT_OPTIONS.map((option, index) => {
593
- const isSelected = index === reasoningEffortIndex;
594
- return (_jsxs(Box, { paddingX: 0, paddingY: 0, children: [_jsxs(Text, { color: isSelected ? 'cyan' : 'gray', bold: isSelected, children: [isSelected ? '› ' : ' ', option.label] }), _jsxs(Text, { color: isSelected ? 'white' : 'gray', children: [' ', "- ", option.description] })] }, option.value));
595
- })] }));
650
+ const reasoningVisibleItems = Math.min(maxVisibleItems, REASONING_EFFORT_OPTIONS.length);
651
+ const reasoningOffset = Math.min(Math.max(0, reasoningEffortIndex - reasoningVisibleItems + 1), Math.max(0, REASONING_EFFORT_OPTIONS.length - reasoningVisibleItems));
652
+ const visibleReasoningOptions = REASONING_EFFORT_OPTIONS.slice(reasoningOffset, reasoningOffset + reasoningVisibleItems);
653
+ const selectedReasoningOption = REASONING_EFFORT_OPTIONS[reasoningEffortIndex];
654
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { paddingX: 0, paddingY: 0, children: _jsxs(Text, { color: "cyan", bold: true, children: ["Reasoning Effort", isSettingDefault ? _jsx(Text, { color: "gray", children: " (default)" }) : null] }) }), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "gray", wrap: "truncate-end", children: pendingReasoningModel.displayName || pendingReasoningModel.name }) }), _jsx(Box, { flexDirection: "column", height: maxVisibleItems, marginTop: 1, children: Array.from({ length: maxVisibleItems }, (_, rowIndex) => {
655
+ const option = visibleReasoningOptions[rowIndex];
656
+ if (!option) {
657
+ return (_jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { children: " " }) }, `reasoning-empty-${rowIndex}`));
658
+ }
659
+ const actualIndex = reasoningOffset + rowIndex;
660
+ const isSelected = actualIndex === reasoningEffortIndex;
661
+ return (_jsx(Box, { paddingX: 0, paddingY: 0, children: _jsxs(Text, { color: isSelected ? 'cyan' : 'gray', bold: isSelected, wrap: "truncate-end", children: [isSelected ? '›' : ' ', " ", option.label] }) }, option.value));
662
+ }) }), _jsx(Box, { paddingX: 0, paddingY: 0, marginTop: 1, children: _jsx(Text, { color: "gray", wrap: "truncate-end", children: selectedReasoningOption?.description ?? '' }) }), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(HintBar, { hints: ['↑↓ navigate', 'Enter select', 'Esc back'] }) })] }));
596
663
  }
597
- const visibleItems = filteredItems.slice(scrollOffset, scrollOffset + MAX_VISIBLE_ITEMS);
598
- const hasCustomModels = customModels.length > 0;
599
- const hasActionableItems = filteredItems.some((item) => !isAddCustomOption(item));
664
+ const selectedIndex = selection.index;
665
+ const scrollOffset = selection.offset;
666
+ const modelsOnly = filteredItems.slice(1);
667
+ const visibleModels = modelsOnly.slice(scrollOffset, scrollOffset + modelsViewportItems);
600
668
  const selectedItem = filteredItems[selectedIndex];
601
- const isSelectedCustom = selectedItem && !isAddCustomOption(selectedItem) && selectedItem.isCustom;
602
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { paddingX: 0, paddingY: 0, children: _jsxs(Text, { color: "cyan", bold: true, children: ["Select Model (", selectedIndex + 1, "/", filteredItems.length, ")"] }) }), _jsxs(Box, { paddingX: 0, paddingY: 0, children: [_jsx(Text, { color: "gray", children: "\u2191\u2193 navigate, Enter select, Esc close" }), hasActionableItems && _jsx(Text, { color: "gray", children: ", \u2192\u2190 for actions" })] }), _jsxs(Box, { paddingX: 0, paddingY: 0, children: [_jsx(Text, { color: "gray", children: "\uD83D\uDD0D " }), _jsx(Text, { color: searchQuery ? 'white' : 'gray', children: searchQuery || 'Type to search...' }), _jsx(Text, { color: "cyan", children: "\u258C" })] }), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "gray", children: '─'.repeat(50) }) }), visibleItems.map((item, visibleIndex) => {
603
- const actualIndex = scrollOffset + visibleIndex;
604
- const isSelected = actualIndex === selectedIndex;
605
- if (isAddCustomOption(item)) {
606
- return (_jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: isSelected ? 'green' : 'gray', bold: isSelected, children: "\u2795 Add custom model..." }) }, "add-custom"));
607
- }
608
- // Show action buttons for selected custom models
609
- const showActions = isSelected && !isAddCustomOption(item);
610
- // Keep the UI label simple: show the actual provider being selected.
611
- // Gateway routing details are intentionally hidden from the main picker.
612
- const providerDisplay = getLLMProviderDisplayName(item.provider);
613
- return (_jsxs(Box, { paddingX: 0, paddingY: 0, children: [item.isCustom && _jsx(Text, { color: isSelected ? 'orange' : 'gray', children: "\u2605 " }), _jsx(Text, { color: isSelected ? 'cyan' : 'gray', bold: isSelected, children: item.displayName || item.name }), _jsxs(Text, { color: isSelected ? 'white' : 'gray', children: [" (", providerDisplay, ")"] }), _jsxs(Text, { color: isSelected ? 'white' : 'gray', children: [' ', "\u2022 ", item.maxInputTokens.toLocaleString(), " tokens"] }), item.isDefault && (_jsx(Text, { color: isSelected ? 'white' : 'gray', children: " [DEFAULT]" })), item.isCurrent && !showActions && (_jsxs(Text, { color: isSelected ? 'cyan' : 'gray', bold: isSelected, children: [' ', "\u2190 Current"] })), showActions && (_jsxs(_Fragment, { children: [item.isCustom && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsxs(Text, { color: customModelAction === 'edit' ? 'green' : 'gray', bold: customModelAction === 'edit', inverse: customModelAction === 'edit', children: [' ', "Edit", ' '] })] })), _jsx(Text, { children: " " }), _jsxs(Text, { color: customModelAction === 'default' ? 'cyan' : 'gray', bold: customModelAction === 'default', inverse: customModelAction === 'default', children: [' ', "Set as Default", ' '] }), item.isCustom && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsxs(Text, { color: customModelAction === 'delete' ? 'red' : 'gray', bold: customModelAction === 'delete', inverse: customModelAction === 'delete', children: [' ', "Delete", ' '] })] }))] }))] }, `${item.provider}-${item.name}-${item.isCustom ? 'custom' : 'registry'}`));
614
- }), filteredItems.length > MAX_VISIBLE_ITEMS && (_jsx(Box, { paddingX: 0, paddingY: 0, children: _jsxs(Text, { color: "gray", wrap: "truncate", children: [scrollOffset > 0 ? '↑ more above' : '', scrollOffset > 0 && scrollOffset + MAX_VISIBLE_ITEMS < filteredItems.length
615
- ? ' '
616
- : '', scrollOffset + MAX_VISIBLE_ITEMS < filteredItems.length
617
- ? '↓ more below'
618
- : ''] }) })), customModelAction === 'delete' && pendingDeleteConfirm && (_jsx(Box, { paddingX: 0, paddingY: 0, marginTop: 1, children: _jsx(Text, { color: "yellowBright", children: "\u26A0\uFE0F Press \u2192 or Enter again to confirm delete" }) })), customModelAction && !pendingDeleteConfirm && (_jsx(Box, { paddingX: 0, paddingY: 0, marginTop: 1, children: _jsxs(Text, { color: "gray", children: ["\u2190", ' ', customModelAction === 'edit'
619
- ? 'deselect'
620
- : isSelectedCustom
621
- ? 'edit'
622
- : 'deselect', ' ', "| \u2192", ' ', customModelAction === 'edit'
623
- ? 'default'
624
- : customModelAction === 'default'
625
- ? isSelectedCustom
626
- ? 'delete'
627
- : 'confirm'
628
- : 'confirm', ' ', "| Enter ", customModelAction] }) }))] }));
669
+ const hasActionableItems = selectedItem && !isAddCustomOption(selectedItem);
670
+ let detailLine = '';
671
+ if (isLoading) {
672
+ detailLine = 'Loading models…';
673
+ }
674
+ else if (customModelAction === 'delete' && pendingDeleteConfirm) {
675
+ detailLine = 'Confirm delete: press Enter again';
676
+ }
677
+ else if (customModelAction) {
678
+ const label = customModelAction === 'edit'
679
+ ? 'Edit'
680
+ : customModelAction === 'default'
681
+ ? 'Set as default'
682
+ : 'Delete';
683
+ detailLine = `Action: ${label}`;
684
+ }
685
+ else if (searchQuery.trim() && filteredItems.length <= 1) {
686
+ detailLine = 'No models match your search';
687
+ }
688
+ else if (!selectedItem) {
689
+ detailLine = '';
690
+ }
691
+ else if (isAddCustomOption(selectedItem)) {
692
+ detailLine = 'Enter to add a custom model';
693
+ }
694
+ else {
695
+ const provider = getLLMProviderDisplayName(selectedItem.provider);
696
+ const name = selectedItem.displayName || selectedItem.name;
697
+ const flags = [];
698
+ if (selectedItem.isDefault)
699
+ flags.push('default');
700
+ if (selectedItem.isCurrent)
701
+ flags.push('current');
702
+ detailLine =
703
+ flags.length > 0
704
+ ? `${name} (${provider}) • ${flags.join(', ')}`
705
+ : `${name} (${provider})`;
706
+ }
707
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "cyan", bold: true, children: "Models" }) }), _jsxs(Box, { paddingX: 0, paddingY: 0, marginTop: 1, children: [_jsx(Text, { color: "gray", children: "Search: " }), _jsx(Text, { color: searchQuery ? 'white' : 'gray', wrap: "truncate-end", children: searchQuery || 'Type to filter models…' })] }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Box, { paddingX: 0, paddingY: 0, children: _jsxs(Text, { color: selectedIndex === 0 ? 'green' : 'gray', bold: selectedIndex === 0, wrap: "truncate-end", children: [getRowPrefix({
708
+ isSelected: selectedIndex === 0,
709
+ isDefault: false,
710
+ isCurrent: false,
711
+ isCustom: false,
712
+ }), ' ', "Add custom model\u2026"] }) }), _jsx(Box, { flexDirection: "column", height: modelsViewportItems, children: isLoading || modelsOnly.length === 0
713
+ ? Array.from({ length: modelsViewportItems }, (_, index) => (_jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { children: " " }) }, `model-empty-${index}`)))
714
+ : Array.from({ length: modelsViewportItems }, (_, rowIndex) => {
715
+ const item = visibleModels[rowIndex];
716
+ if (!item) {
717
+ return (_jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { children: " " }) }, `model-empty-${rowIndex}`));
718
+ }
719
+ const actualIndex = 1 + scrollOffset + rowIndex;
720
+ const isSelected = actualIndex === selectedIndex;
721
+ const providerDisplay = getLLMProviderDisplayName(item.provider);
722
+ const name = item.displayName || item.name;
723
+ const prefix = getRowPrefix({
724
+ isSelected,
725
+ isDefault: item.isDefault,
726
+ isCurrent: item.isCurrent,
727
+ isCustom: item.isCustom,
728
+ });
729
+ return (_jsxs(Box, { flexDirection: "row", paddingX: 0, paddingY: 0, children: [_jsx(Box, { flexGrow: 1, children: _jsxs(Text, { color: isSelected ? 'cyan' : 'gray', bold: isSelected, wrap: "truncate-end", children: [prefix, " ", name, " (", providerDisplay, ")"] }) }), isSelected && (_jsxs(Box, { flexDirection: "row", marginLeft: 1, children: [item.isCustom && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: customModelAction === 'edit'
730
+ ? 'green'
731
+ : 'gray', bold: customModelAction === 'edit', inverse: customModelAction === 'edit', children: [' ', "Edit", ' '] }), _jsx(Text, { children: " " })] })), _jsxs(Text, { color: customModelAction === 'default'
732
+ ? 'cyan'
733
+ : 'gray', bold: customModelAction === 'default', inverse: customModelAction === 'default', children: [' ', "Set as Default", ' '] }), item.isCustom && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsxs(Text, { color: customModelAction === 'delete'
734
+ ? 'red'
735
+ : 'gray', bold: customModelAction === 'delete', inverse: customModelAction === 'delete', children: [' ', "Delete", ' '] })] }))] }))] }, `${item.provider}-${item.name}-${item.isCustom ? 'custom' : 'registry'}`));
736
+ }) })] }), _jsx(Box, { paddingX: 0, paddingY: 0, marginTop: 1, children: _jsx(Text, { color: "gray", wrap: "truncate-end", children: detailLine }) }), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(HintBar, { hints: [
737
+ '↑↓ navigate',
738
+ 'Enter select',
739
+ 'Esc close',
740
+ hasActionableItems ? '←→ actions' : '',
741
+ ] }) })] }));
629
742
  });
630
743
  export default ModelSelector;