centaurus-cli 2.8.8 → 2.9.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.
Files changed (134) hide show
  1. package/dist/cli-adapter.d.ts +82 -2
  2. package/dist/cli-adapter.d.ts.map +1 -1
  3. package/dist/cli-adapter.js +622 -94
  4. package/dist/cli-adapter.js.map +1 -1
  5. package/dist/config/ConfigManager.d.ts.map +1 -1
  6. package/dist/config/ConfigManager.js +6 -5
  7. package/dist/config/ConfigManager.js.map +1 -1
  8. package/dist/config/build-config.d.ts +42 -0
  9. package/dist/config/build-config.d.ts.map +1 -0
  10. package/dist/config/build-config.js +44 -0
  11. package/dist/config/build-config.js.map +1 -0
  12. package/dist/config/manager.d.ts +2 -2
  13. package/dist/config/manager.d.ts.map +1 -1
  14. package/dist/config/manager.js +9 -12
  15. package/dist/config/manager.js.map +1 -1
  16. package/dist/config/mcp-config-manager.d.ts +5 -0
  17. package/dist/config/mcp-config-manager.d.ts.map +1 -1
  18. package/dist/config/mcp-config-manager.js +8 -0
  19. package/dist/config/mcp-config-manager.js.map +1 -1
  20. package/dist/config/models.d.ts +48 -42
  21. package/dist/config/models.d.ts.map +1 -1
  22. package/dist/config/models.js +148 -133
  23. package/dist/config/models.js.map +1 -1
  24. package/dist/config/slash-commands.d.ts +2 -0
  25. package/dist/config/slash-commands.d.ts.map +1 -1
  26. package/dist/config/slash-commands.js +34 -1
  27. package/dist/config/slash-commands.js.map +1 -1
  28. package/dist/context/context-manager.d.ts.map +1 -1
  29. package/dist/context/context-manager.js +6 -6
  30. package/dist/context/context-manager.js.map +1 -1
  31. package/dist/hooks/useConnectivity.d.ts +2 -0
  32. package/dist/hooks/useConnectivity.d.ts.map +1 -0
  33. package/dist/hooks/useConnectivity.js +12 -0
  34. package/dist/hooks/useConnectivity.js.map +1 -0
  35. package/dist/index.d.ts +6 -0
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +37 -23
  38. package/dist/index.js.map +1 -1
  39. package/dist/mcp/mcp-command-handler.d.ts.map +1 -1
  40. package/dist/mcp/mcp-command-handler.js +2 -5
  41. package/dist/mcp/mcp-command-handler.js.map +1 -1
  42. package/dist/mcp/mcp-tool-wrapper.d.ts.map +1 -1
  43. package/dist/mcp/mcp-tool-wrapper.js +8 -0
  44. package/dist/mcp/mcp-tool-wrapper.js.map +1 -1
  45. package/dist/services/ai-service-client.d.ts +1 -0
  46. package/dist/services/ai-service-client.d.ts.map +1 -1
  47. package/dist/services/ai-service-client.js +8 -6
  48. package/dist/services/ai-service-client.js.map +1 -1
  49. package/dist/services/api-client.d.ts +46 -34
  50. package/dist/services/api-client.d.ts.map +1 -1
  51. package/dist/services/api-client.js +47 -37
  52. package/dist/services/api-client.js.map +1 -1
  53. package/dist/services/background-task-manager.d.ts +114 -0
  54. package/dist/services/background-task-manager.d.ts.map +1 -0
  55. package/dist/services/background-task-manager.js +301 -0
  56. package/dist/services/background-task-manager.js.map +1 -0
  57. package/dist/services/connectivity-manager.d.ts +18 -0
  58. package/dist/services/connectivity-manager.d.ts.map +1 -0
  59. package/dist/services/connectivity-manager.js +72 -0
  60. package/dist/services/connectivity-manager.js.map +1 -0
  61. package/dist/services/local-chat-storage.d.ts +5 -0
  62. package/dist/services/local-chat-storage.d.ts.map +1 -1
  63. package/dist/services/local-chat-storage.js +38 -4
  64. package/dist/services/local-chat-storage.js.map +1 -1
  65. package/dist/tools/background-command.d.ts +11 -0
  66. package/dist/tools/background-command.d.ts.map +1 -0
  67. package/dist/tools/background-command.js +162 -0
  68. package/dist/tools/background-command.js.map +1 -0
  69. package/dist/tools/command.d.ts.map +1 -1
  70. package/dist/tools/command.js +6 -3
  71. package/dist/tools/command.js.map +1 -1
  72. package/dist/tools/create-image.d.ts +10 -0
  73. package/dist/tools/create-image.d.ts.map +1 -0
  74. package/dist/tools/create-image.js +189 -0
  75. package/dist/tools/create-image.js.map +1 -0
  76. package/dist/tools/web-search.d.ts.map +1 -1
  77. package/dist/tools/web-search.js +8 -6
  78. package/dist/tools/web-search.js.map +1 -1
  79. package/dist/ui/components/App.d.ts +34 -2
  80. package/dist/ui/components/App.d.ts.map +1 -1
  81. package/dist/ui/components/App.js +403 -67
  82. package/dist/ui/components/App.js.map +1 -1
  83. package/dist/ui/components/ContextWindowIndicator.d.ts.map +1 -1
  84. package/dist/ui/components/ContextWindowIndicator.js +43 -22
  85. package/dist/ui/components/ContextWindowIndicator.js.map +1 -1
  86. package/dist/ui/components/ErrorBoundary.d.ts.map +1 -1
  87. package/dist/ui/components/ErrorBoundary.js +2 -1
  88. package/dist/ui/components/ErrorBoundary.js.map +1 -1
  89. package/dist/ui/components/InputBox.d.ts +4 -0
  90. package/dist/ui/components/InputBox.d.ts.map +1 -1
  91. package/dist/ui/components/InputBox.js +289 -207
  92. package/dist/ui/components/InputBox.js.map +1 -1
  93. package/dist/ui/components/MessageDisplay.d.ts.map +1 -1
  94. package/dist/ui/components/MessageDisplay.js +8 -15
  95. package/dist/ui/components/MessageDisplay.js.map +1 -1
  96. package/dist/ui/components/SlashCommandAutocomplete.d.ts +2 -0
  97. package/dist/ui/components/SlashCommandAutocomplete.d.ts.map +1 -1
  98. package/dist/ui/components/SlashCommandAutocomplete.js +19 -10
  99. package/dist/ui/components/SlashCommandAutocomplete.js.map +1 -1
  100. package/dist/ui/components/StatusBar.d.ts.map +1 -1
  101. package/dist/ui/components/StatusBar.js +4 -0
  102. package/dist/ui/components/StatusBar.js.map +1 -1
  103. package/dist/ui/components/ToolExecutionMessage.d.ts.map +1 -1
  104. package/dist/ui/components/ToolExecutionMessage.js +155 -41
  105. package/dist/ui/components/ToolExecutionMessage.js.map +1 -1
  106. package/dist/ui/components/ToolExecutionStatus.d.ts.map +1 -1
  107. package/dist/ui/components/ToolExecutionStatus.js +1 -0
  108. package/dist/ui/components/ToolExecutionStatus.js.map +1 -1
  109. package/dist/utils/chat-formatter.d.ts +12 -0
  110. package/dist/utils/chat-formatter.d.ts.map +1 -0
  111. package/dist/utils/chat-formatter.js +326 -0
  112. package/dist/utils/chat-formatter.js.map +1 -0
  113. package/dist/utils/command-history.d.ts.map +1 -1
  114. package/dist/utils/command-history.js +2 -1
  115. package/dist/utils/command-history.js.map +1 -1
  116. package/dist/utils/conversation-logger.d.ts +15 -0
  117. package/dist/utils/conversation-logger.d.ts.map +1 -1
  118. package/dist/utils/conversation-logger.js +56 -2
  119. package/dist/utils/conversation-logger.js.map +1 -1
  120. package/dist/utils/editor-utils.d.ts.map +1 -1
  121. package/dist/utils/editor-utils.js +3 -2
  122. package/dist/utils/editor-utils.js.map +1 -1
  123. package/dist/utils/input-classifier.d.ts.map +1 -1
  124. package/dist/utils/input-classifier.js +140 -20
  125. package/dist/utils/input-classifier.js.map +1 -1
  126. package/dist/utils/logger.d.ts.map +1 -1
  127. package/dist/utils/logger.js +31 -1
  128. package/dist/utils/logger.js.map +1 -1
  129. package/dist/utils/text-clipboard.d.ts +12 -0
  130. package/dist/utils/text-clipboard.d.ts.map +1 -0
  131. package/dist/utils/text-clipboard.js +63 -0
  132. package/dist/utils/text-clipboard.js.map +1 -0
  133. package/package.json +1 -2
  134. package/models-config.json +0 -126
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useRef, useMemo } from 'react';
2
2
  import { Box, Text, useInput } from 'ink';
3
3
  import * as fs from 'fs';
4
4
  import * as path from 'path';
5
+ import { useConnectivity } from '../../hooks/useConnectivity.js';
5
6
  import { Breadcrumbs } from './Breadcrumbs.js';
6
7
  import { ContextWindowIndicator } from './ContextWindowIndicator.js';
7
8
  import { logDebug } from '../../utils/logger.js';
@@ -9,9 +10,9 @@ import { detectIntent } from '../../utils/input-classifier.js';
9
10
  import { CommandHistoryManager } from '../../utils/command-history.js';
10
11
  import { SlashCommandAutocomplete } from './SlashCommandAutocomplete.js';
11
12
  import { FileTagAutocomplete } from './FileTagAutocomplete.js';
12
- import { ClipboardImageAutocomplete } from './ClipboardImageAutocomplete.js';
13
13
  import { filterCommands } from '../../config/slash-commands.js';
14
14
  import { getClipboardImages } from '../../services/clipboard-service.js';
15
+ import { useTerminalDimensions, TERMINAL_HEIGHT_CONSTANTS } from '../../hooks/useTerminalDimensions.js';
15
16
  const getVisualLines = (text, width) => {
16
17
  const logicalLines = text.split('\n');
17
18
  const visualLines = [];
@@ -49,7 +50,7 @@ const getVisualLines = (text, width) => {
49
50
  });
50
51
  return visualLines;
51
52
  };
52
- export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...', autoAcceptMode, model, planMode = false, commandMode = false, currentWorkingDirectory, commandHistory = [], onToggleAutoAccept, onToggleCommandMode, isActive = true, subshellContext, currentTokens = 0, maxTokens = 1000000, isShellRunning = false, initialValue = '', onValueChange }) => {
53
+ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...', autoAcceptMode, model, planMode = false, commandMode = false, backgroundMode = false, currentWorkingDirectory, commandHistory = [], onToggleAutoAccept, onToggleCommandMode, onToggleBackgroundMode, isActive = true, subshellContext, currentTokens = 0, maxTokens = 1000000, isShellRunning = false, backgroundTaskCount = 0, initialValue = '', onValueChange, onSetAutoModeSetup }) => {
53
54
  // Use initialValue for first mount, but manage state internally after that
54
55
  const [value, setValueInternal] = useState(initialValue);
55
56
  const [cursorOffset, setCursorOffset] = useState(0);
@@ -58,6 +59,10 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
58
59
  const [historyIndex, setHistoryIndex] = useState(-1);
59
60
  const [tempValue, setTempValue] = useState('');
60
61
  const ignoreNextChangeRef = useRef(false);
62
+ // Refs to track current value and cursor for paste handling
63
+ // This prevents stale closure issues when Ink calls useInput multiple times during paste
64
+ const valueRef = useRef(initialValue);
65
+ const cursorOffsetRef = useRef(0);
61
66
  // Auto Mode State
62
67
  const [isAutoMode, setIsAutoMode] = useState(true);
63
68
  const [detectedIntent, setDetectedIntent] = useState('ai');
@@ -74,30 +79,34 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
74
79
  const [slashAutocompleteVisible, setSlashAutocompleteVisible] = useState(false);
75
80
  const [slashAutocompleteCommands, setSlashAutocompleteCommands] = useState([]);
76
81
  const [slashAutocompleteSelectedIndex, setSlashAutocompleteSelectedIndex] = useState(0);
82
+ const [slashAutocompleteScrollOffset, setSlashAutocompleteScrollOffset] = useState(0);
83
+ // Connectivity State
84
+ const isConnected = useConnectivity();
85
+ // Terminal dimensions for height-aware autocomplete
86
+ const dimensions = useTerminalDimensions();
87
+ // Max 5 items, min 0 for very small terminals (use MIN_ROWS_FOR_STREAMING as threshold)
88
+ const slashMaxVisibleItems = dimensions.rows < TERMINAL_HEIGHT_CONSTANTS.MIN_ROWS_FOR_STREAMING
89
+ ? 0
90
+ : Math.min(5, Math.max(1, Math.floor((dimensions.rows - 20) / 3)));
77
91
  // File Tag Autocomplete State (@ symbol)
78
92
  const [fileTagAutocompleteVisible, setFileTagAutocompleteVisible] = useState(false);
79
93
  const [fileTagSuggestions, setFileTagSuggestions] = useState([]);
80
94
  const [fileTagSelectedIndex, setFileTagSelectedIndex] = useState(0);
81
95
  const [activeFileTagStart, setActiveFileTagStart] = useState(null);
82
96
  const [confirmedFileTags, setConfirmedFileTags] = useState([]);
83
- // Clipboard Image State (#image command)
84
- const [clipboardAutocompleteVisible, setClipboardAutocompleteVisible] = useState(false);
85
- const [clipboardImages, setClipboardImages] = useState([]);
86
- const [clipboardSelectedIndex, setClipboardSelectedIndex] = useState(0);
87
- const [clipboardLoading, setClipboardLoading] = useState(false);
88
- const [activeClipboardStart, setActiveClipboardStart] = useState(null);
97
+ // Clipboard Image State (Alt+V paste)
89
98
  const [confirmedClipboardImages, setConfirmedClipboardImages] = useState([]);
90
- // Track positions of validated #image commands (for pink highlighting)
91
- const [validatedImagePositions, setValidatedImagePositions] = useState([]);
92
99
  // Track visual line count to force re-renders when text wraps
93
100
  // This is necessary because Ink doesn't automatically update layout when text wraps
94
101
  const [visualLineCount, setVisualLineCount] = useState(1);
95
102
  // Configuration for scrolling
96
103
  const MAX_VISIBLE_LINES = 9;
97
- // Wrapper for setValue that also notifies parent of changes
104
+ // Wrapper for setValue that also notifies parent of changes and updates ref
98
105
  const setValue = React.useCallback((newValue) => {
99
106
  setValueInternal(prev => {
100
107
  const resolvedValue = typeof newValue === 'function' ? newValue(prev) : newValue;
108
+ // Update ref synchronously for paste handling
109
+ valueRef.current = resolvedValue;
101
110
  // Notify parent of value change for preservation across screen transitions
102
111
  if (onValueChange) {
103
112
  onValueChange(resolvedValue);
@@ -105,16 +114,53 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
105
114
  return resolvedValue;
106
115
  });
107
116
  }, [onValueChange]);
117
+ // Wrapper for setCursorOffset that also updates ref
118
+ const setCursorOffsetWithRef = React.useCallback((newOffset) => {
119
+ setCursorOffset(prev => {
120
+ const resolvedOffset = typeof newOffset === 'function' ? newOffset(prev) : newOffset;
121
+ // Update ref synchronously for paste handling
122
+ cursorOffsetRef.current = resolvedOffset;
123
+ return resolvedOffset;
124
+ });
125
+ }, []);
108
126
  // Initialize cursor position when initialValue is provided
109
127
  useEffect(() => {
110
128
  if (initialValue && initialValue.length > 0) {
111
129
  setCursorOffset(initialValue.length);
130
+ cursorOffsetRef.current = initialValue.length;
112
131
  }
113
132
  }, []); // Only run on mount
133
+ // Keep refs in sync with state (for cases where state is updated directly)
134
+ useEffect(() => {
135
+ valueRef.current = value;
136
+ }, [value]);
137
+ useEffect(() => {
138
+ cursorOffsetRef.current = cursorOffset;
139
+ }, [cursorOffset]);
114
140
  // Load history on mount
115
141
  useEffect(() => {
116
142
  CommandHistoryManager.getInstance().load();
117
143
  }, []);
144
+ // Register setIsAutoMode callback for external control (e.g., after background task starts)
145
+ useEffect(() => {
146
+ if (onSetAutoModeSetup) {
147
+ onSetAutoModeSetup((enabled) => {
148
+ setIsAutoMode(enabled);
149
+ if (enabled) {
150
+ // When enabling Auto mode, also update detected intent based on current value
151
+ const intent = detectIntent(value);
152
+ setDetectedIntent(intent);
153
+ // Set command mode based on intent
154
+ if (intent === 'command' && !commandMode && onToggleCommandMode) {
155
+ onToggleCommandMode();
156
+ }
157
+ else if (intent === 'ai' && commandMode && onToggleCommandMode) {
158
+ onToggleCommandMode();
159
+ }
160
+ }
161
+ });
162
+ }
163
+ }, [onSetAutoModeSetup, value, commandMode, onToggleCommandMode]);
118
164
  // Force clear value if it becomes empty or whitespace-only after external changes
119
165
  useEffect(() => {
120
166
  if (value && value.trim() === '' && cursorOffset > 0) {
@@ -269,122 +315,6 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
269
315
  setActiveFileTagStart(null);
270
316
  }
271
317
  }, [value, cursorOffset, commandMode, currentWorkingDirectory]);
272
- // Hash (#) symbol detection effect for clipboard image autocomplete
273
- // When user types # in Agent mode, check clipboard for images and show dropdown
274
- useEffect(() => {
275
- // Don't show in command mode
276
- if (commandMode) {
277
- setClipboardAutocompleteVisible(false);
278
- setActiveClipboardStart(null);
279
- return;
280
- }
281
- // Find if cursor is right after a # character (or typing after #)
282
- let hashPosition = -1;
283
- for (let i = cursorOffset - 1; i >= 0; i--) {
284
- const char = value[i];
285
- // Stop if we hit whitespace
286
- if (/[\s\n]/.test(char))
287
- break;
288
- if (char === '#') {
289
- hashPosition = i;
290
- break;
291
- }
292
- }
293
- // Only treat # as image trigger if it's at start of input OR preceded by whitespace
294
- if (hashPosition === -1 || (hashPosition > 0 && !/[\s\n]/.test(value[hashPosition - 1]))) {
295
- setClipboardAutocompleteVisible(false);
296
- setActiveClipboardStart(null);
297
- return;
298
- }
299
- // Extract what's typed after #
300
- const query = value.slice(hashPosition + 1, cursorOffset).toLowerCase();
301
- // If #image is fully typed (with space or at end), hide dropdown (validation will handle it)
302
- if (query === 'image' || query.startsWith('image ')) {
303
- setClipboardAutocompleteVisible(false);
304
- setActiveClipboardStart(null);
305
- return;
306
- }
307
- // If user has typed something that doesn't match "image" prefix, hide dropdown
308
- if (query.length > 0 && !'image'.startsWith(query)) {
309
- setClipboardAutocompleteVisible(false);
310
- setActiveClipboardStart(null);
311
- return;
312
- }
313
- // Show dropdown and check clipboard
314
- setActiveClipboardStart(hashPosition);
315
- setClipboardLoading(true);
316
- setClipboardAutocompleteVisible(true);
317
- // Check clipboard asynchronously
318
- const checkClipboard = async () => {
319
- try {
320
- const images = await getClipboardImages();
321
- setClipboardImages(images);
322
- setClipboardLoading(false);
323
- setClipboardSelectedIndex(0);
324
- }
325
- catch (error) {
326
- logDebug(`Failed to check clipboard: ${error instanceof Error ? error.message : 'Unknown error'}`);
327
- setClipboardImages([]);
328
- setClipboardLoading(false);
329
- }
330
- };
331
- checkClipboard();
332
- }, [value, cursorOffset, commandMode]);
333
- // #image command detection effect
334
- // When user types #image, check clipboard for images and validate
335
- useEffect(() => {
336
- // Don't check in command mode
337
- if (commandMode)
338
- return;
339
- // Find all #image occurrences in the text
340
- const regex = /#image\b/gi;
341
- const matches = [];
342
- let match;
343
- while ((match = regex.exec(value)) !== null) {
344
- matches.push({ start: match.index, end: match.index + match[0].length });
345
- }
346
- // If no #image in text, clear validated positions
347
- if (matches.length === 0) {
348
- if (validatedImagePositions.length > 0) {
349
- setValidatedImagePositions([]);
350
- setConfirmedClipboardImages([]);
351
- }
352
- return;
353
- }
354
- // Check for new #image occurrences that haven't been validated yet
355
- const unvalidatedMatches = matches.filter(m => !validatedImagePositions.some(v => v.start === m.start && v.end === m.end));
356
- if (unvalidatedMatches.length === 0)
357
- return;
358
- // Check clipboard and validate new #image occurrences
359
- const validateImages = async () => {
360
- logDebug(`Found ${unvalidatedMatches.length} new #image occurrences to validate`);
361
- try {
362
- const images = await getClipboardImages();
363
- logDebug(`Clipboard check returned ${images.length} images`);
364
- if (images.length > 0) {
365
- const image = images[0];
366
- logDebug(`Image found in clipboard: ${image.displayName}, ${image.sizeBytes} bytes`);
367
- // Add to confirmed clipboard images (avoid duplicates)
368
- setConfirmedClipboardImages(prev => {
369
- const exists = prev.some(img => img.id === image.id);
370
- if (exists)
371
- return prev;
372
- return [...prev, image];
373
- });
374
- // Mark all unvalidated positions as validated
375
- setValidatedImagePositions(prev => [...prev, ...unvalidatedMatches]);
376
- logDebug(`Validated ${unvalidatedMatches.length} #image occurrences`);
377
- }
378
- else {
379
- logDebug('No image in clipboard - #image command not validated');
380
- }
381
- }
382
- catch (error) {
383
- logDebug(`Failed to check clipboard: ${error instanceof Error ? error.message : 'Unknown error'}`);
384
- }
385
- };
386
- validateImages();
387
- }, [value, commandMode]);
388
318
  const pushToUndoStack = () => {
389
319
  setUndoStack(prev => [...prev, { value, cursorOffset }]);
390
320
  setRedoStack([]); // Clear redo stack on new action
@@ -408,11 +338,21 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
408
338
  // Handle slash command autocomplete navigation
409
339
  if (slashAutocompleteVisible) {
410
340
  if (key.downArrow) {
411
- setSlashAutocompleteSelectedIndex(prev => Math.min(prev + 1, slashAutocompleteCommands.length - 1));
341
+ const newIndex = Math.min(slashAutocompleteSelectedIndex + 1, slashAutocompleteCommands.length - 1);
342
+ setSlashAutocompleteSelectedIndex(newIndex);
343
+ // Scroll down if selected is below visible window
344
+ if (newIndex >= slashAutocompleteScrollOffset + slashMaxVisibleItems) {
345
+ setSlashAutocompleteScrollOffset(newIndex - slashMaxVisibleItems + 1);
346
+ }
412
347
  return;
413
348
  }
414
349
  if (key.upArrow) {
415
- setSlashAutocompleteSelectedIndex(prev => Math.max(prev - 1, 0));
350
+ const newIndex = Math.max(slashAutocompleteSelectedIndex - 1, 0);
351
+ setSlashAutocompleteSelectedIndex(newIndex);
352
+ // Scroll up if selected is above visible window
353
+ if (newIndex < slashAutocompleteScrollOffset) {
354
+ setSlashAutocompleteScrollOffset(newIndex);
355
+ }
416
356
  return;
417
357
  }
418
358
  if (key.return && input.length <= 1 && !key.shift && !key.ctrl) {
@@ -441,6 +381,21 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
441
381
  setCursorOffset(newValue.length);
442
382
  setSlashAutocompleteVisible(false);
443
383
  }
384
+ else if (value.startsWith('/background-task ') || value.startsWith('/bkg ') || value.startsWith('/bg-task ')) {
385
+ // We're selecting a background-task subcommand
386
+ const prefix = value.startsWith('/bkg ') ? '/bkg ' : (value.startsWith('/bg-task ') ? '/bg-task ' : '/background-task ');
387
+ const newValue = `${prefix}${selected.name} `;
388
+ setValue(newValue);
389
+ setCursorOffset(newValue.length);
390
+ setSlashAutocompleteVisible(false);
391
+ }
392
+ else if (value.startsWith('/sync ')) {
393
+ // We're selecting a sync subcommand
394
+ const newValue = `/sync ${selected.name} `;
395
+ setValue(newValue);
396
+ setCursorOffset(newValue.length);
397
+ setSlashAutocompleteVisible(false);
398
+ }
444
399
  else {
445
400
  // Regular slash command, replace everything
446
401
  const newValue = `/${selected.name} `;
@@ -453,6 +408,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
453
408
  if (subcommandMatches.length > 0) {
454
409
  setSlashAutocompleteCommands(subcommandMatches);
455
410
  setSlashAutocompleteSelectedIndex(0);
411
+ setSlashAutocompleteScrollOffset(0);
456
412
  // Keep autocomplete visible for subcommands
457
413
  }
458
414
  else {
@@ -464,6 +420,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
464
420
  if (subcommandMatches.length > 0) {
465
421
  setSlashAutocompleteCommands(subcommandMatches);
466
422
  setSlashAutocompleteSelectedIndex(0);
423
+ setSlashAutocompleteScrollOffset(0);
467
424
  // Keep autocomplete visible for subcommands
468
425
  }
469
426
  else {
@@ -475,6 +432,31 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
475
432
  if (subcommandMatches.length > 0) {
476
433
  setSlashAutocompleteCommands(subcommandMatches);
477
434
  setSlashAutocompleteSelectedIndex(0);
435
+ setSlashAutocompleteScrollOffset(0);
436
+ // Keep autocomplete visible for subcommands
437
+ }
438
+ else {
439
+ setSlashAutocompleteVisible(false);
440
+ }
441
+ }
442
+ else if (selected.name === 'background-task' || selected.name === 'bkg' || selected.name === 'bg-task') {
443
+ const subcommandMatches = filterCommands('background-task ');
444
+ if (subcommandMatches.length > 0) {
445
+ setSlashAutocompleteCommands(subcommandMatches);
446
+ setSlashAutocompleteSelectedIndex(0);
447
+ setSlashAutocompleteScrollOffset(0);
448
+ // Keep autocomplete visible for subcommands
449
+ }
450
+ else {
451
+ setSlashAutocompleteVisible(false);
452
+ }
453
+ }
454
+ else if (selected.name === 'sync') {
455
+ const subcommandMatches = filterCommands('sync ');
456
+ if (subcommandMatches.length > 0) {
457
+ setSlashAutocompleteCommands(subcommandMatches);
458
+ setSlashAutocompleteSelectedIndex(0);
459
+ setSlashAutocompleteScrollOffset(0);
478
460
  // Keep autocomplete visible for subcommands
479
461
  }
480
462
  else {
@@ -565,54 +547,36 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
565
547
  return;
566
548
  }
567
549
  }
568
- // Handle clipboard image (#) autocomplete navigation
569
- if (clipboardAutocompleteVisible) {
570
- if (key.downArrow) {
571
- setClipboardSelectedIndex(prev => Math.min(prev + 1, Math.max(clipboardImages.length - 1, 0)));
572
- return;
573
- }
574
- if (key.upArrow) {
575
- setClipboardSelectedIndex(prev => Math.max(prev - 1, 0));
576
- return;
577
- }
578
- if (key.return && input.length <= 1 && !key.shift && !key.ctrl) {
579
- // Select the image option and autocomplete #image
580
- if (clipboardImages.length > 0 && activeClipboardStart !== null) {
581
- pushToUndoStack();
582
- // Replace # with #image (and add space after)
583
- const beforeHash = value.slice(0, activeClipboardStart);
584
- const afterCursor = value.slice(cursorOffset);
585
- const newValue = beforeHash + '#image ' + afterCursor;
586
- const newCursorPos = activeClipboardStart + 7; // length of "#image "
587
- setValue(newValue);
588
- setCursorOffset(newCursorPos);
589
- setClipboardAutocompleteVisible(false);
590
- setActiveClipboardStart(null);
550
+ // Alt+V: Paste image from clipboard
551
+ // Detect Alt+V on Windows/Linux (key.meta is often Alt on Windows in Ink)
552
+ const isAltV = (key.meta && input === 'v') || (input === '√'); // '√' is Alt+V on some systems
553
+ if (isAltV && !commandMode) {
554
+ // Check clipboard for images asynchronously
555
+ (async () => {
556
+ try {
557
+ const images = await getClipboardImages();
558
+ if (images.length > 0) {
559
+ const image = images[0];
560
+ logDebug(`Alt+V: Image found in clipboard: ${image.displayName}, ${image.sizeBytes} bytes`);
561
+ // Add image to confirmed list with unique ID
562
+ setConfirmedClipboardImages(prev => [
563
+ ...prev,
564
+ {
565
+ ...image,
566
+ id: `${image.id}_${Date.now()}`
567
+ }
568
+ ]);
569
+ }
570
+ else {
571
+ logDebug('Alt+V: No image in clipboard');
572
+ }
591
573
  }
592
- return;
593
- }
594
- if (key.escape) {
595
- setClipboardAutocompleteVisible(false);
596
- setActiveClipboardStart(null);
597
- return;
598
- }
599
- // Tab also selects
600
- if (key.tab && !key.shift) {
601
- if (clipboardImages.length > 0 && activeClipboardStart !== null) {
602
- pushToUndoStack();
603
- const beforeHash = value.slice(0, activeClipboardStart);
604
- const afterCursor = value.slice(cursorOffset);
605
- const newValue = beforeHash + '#image ' + afterCursor;
606
- const newCursorPos = activeClipboardStart + 7;
607
- setValue(newValue);
608
- setCursorOffset(newCursorPos);
609
- setClipboardAutocompleteVisible(false);
610
- setActiveClipboardStart(null);
574
+ catch (error) {
575
+ logDebug(`Alt+V: Failed to check clipboard: ${error instanceof Error ? error.message : 'Unknown error'}`);
611
576
  }
612
- return;
613
- }
577
+ })();
578
+ return;
614
579
  }
615
- // Clipboard image paste is handled via Ctrl+V below
616
580
  // DELETE WORD BACKWARDS - Check this FIRST before standard backspace/delete
617
581
  // Triggers on any of these conditions:
618
582
  // 1. Ctrl+W (char code 23) - Standard Unix terminal shortcut
@@ -646,6 +610,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
646
610
  setSlashAutocompleteCommands(matches);
647
611
  setSlashAutocompleteVisible(true);
648
612
  setSlashAutocompleteSelectedIndex(0);
613
+ setSlashAutocompleteScrollOffset(0);
649
614
  }
650
615
  else {
651
616
  setSlashAutocompleteVisible(false);
@@ -659,6 +624,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
659
624
  setSlashAutocompleteCommands(matches);
660
625
  setSlashAutocompleteVisible(true);
661
626
  setSlashAutocompleteSelectedIndex(0);
627
+ setSlashAutocompleteScrollOffset(0);
662
628
  }
663
629
  else {
664
630
  setSlashAutocompleteVisible(false);
@@ -672,6 +638,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
672
638
  setSlashAutocompleteCommands(matches);
673
639
  setSlashAutocompleteVisible(true);
674
640
  setSlashAutocompleteSelectedIndex(0);
641
+ setSlashAutocompleteScrollOffset(0);
675
642
  }
676
643
  else {
677
644
  setSlashAutocompleteVisible(false);
@@ -685,6 +652,35 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
685
652
  setSlashAutocompleteCommands(matches);
686
653
  setSlashAutocompleteVisible(true);
687
654
  setSlashAutocompleteSelectedIndex(0);
655
+ setSlashAutocompleteScrollOffset(0);
656
+ }
657
+ else {
658
+ setSlashAutocompleteVisible(false);
659
+ }
660
+ }
661
+ else if (newValue.startsWith('/background-task ') || newValue.startsWith('/bkg ') || newValue.startsWith('/bg-task ')) {
662
+ // Background-task subcommands
663
+ const fullQuery = newValue.slice(1);
664
+ const matches = filterCommands(fullQuery);
665
+ if (matches.length > 0) {
666
+ setSlashAutocompleteCommands(matches);
667
+ setSlashAutocompleteVisible(true);
668
+ setSlashAutocompleteSelectedIndex(0);
669
+ setSlashAutocompleteScrollOffset(0);
670
+ }
671
+ else {
672
+ setSlashAutocompleteVisible(false);
673
+ }
674
+ }
675
+ else if (newValue.startsWith('/sync ')) {
676
+ // Sync subcommands
677
+ const fullQuery = newValue.slice(1);
678
+ const matches = filterCommands(fullQuery);
679
+ if (matches.length > 0) {
680
+ setSlashAutocompleteCommands(matches);
681
+ setSlashAutocompleteVisible(true);
682
+ setSlashAutocompleteSelectedIndex(0);
683
+ setSlashAutocompleteScrollOffset(0);
688
684
  }
689
685
  else {
690
686
  setSlashAutocompleteVisible(false);
@@ -705,32 +701,41 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
705
701
  setTimeout(() => { ignoreNextChangeRef.current = false; }, 100);
706
702
  return;
707
703
  }
708
- // Ctrl+D: Cycle modes (Agent -> Terminal -> Auto -> Agent)
704
+ // Ctrl+D: Cycle modes (Agent -> Terminal -> Background -> Auto -> Agent)
709
705
  if (key.ctrl && input.toLowerCase() === 'd') {
710
706
  if (onToggleCommandMode) {
711
707
  ignoreNextChangeRef.current = true;
712
- // Cycle Logic
713
- if (!isAutoMode && !commandMode) {
708
+ // Cycle Logic: Agent -> Terminal -> Background -> Auto -> Agent
709
+ if (!isAutoMode && !commandMode && !backgroundMode) {
714
710
  // Agent -> Terminal
715
711
  onToggleCommandMode();
716
712
  }
717
- else if (!isAutoMode && commandMode) {
718
- // Terminal -> Auto
713
+ else if (!isAutoMode && commandMode && !backgroundMode) {
714
+ // Terminal -> Background
715
+ onToggleCommandMode(); // Exit terminal mode
716
+ if (onToggleBackgroundMode)
717
+ onToggleBackgroundMode(); // Enter background mode
718
+ }
719
+ else if (!isAutoMode && !commandMode && backgroundMode) {
720
+ // Background -> Auto
721
+ if (onToggleBackgroundMode)
722
+ onToggleBackgroundMode(); // Exit background mode
719
723
  setIsAutoMode(true);
720
724
  // Trigger initial detection for Auto mode
721
725
  const intent = detectIntent(value);
722
726
  setDetectedIntent(intent);
723
727
  // Set command mode based on intent
724
- if (intent === 'ai' && commandMode)
728
+ if (intent === 'command')
725
729
  onToggleCommandMode();
726
- // if intent is command, we are already in command mode
727
730
  }
728
731
  else if (isAutoMode) {
729
732
  // Auto -> Agent
730
733
  setIsAutoMode(false);
731
- // Ensure we go back to Agent mode (commandMode = false)
734
+ // Ensure we go back to Agent mode (commandMode = false, backgroundMode = false)
732
735
  if (commandMode)
733
736
  onToggleCommandMode();
737
+ if (backgroundMode && onToggleBackgroundMode)
738
+ onToggleBackgroundMode();
734
739
  }
735
740
  setTimeout(() => { ignoreNextChangeRef.current = false; }, 100);
736
741
  }
@@ -747,7 +752,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
747
752
  setCursorOffset(value.length);
748
753
  return;
749
754
  }
750
- // Note: Clipboard images are handled via #image command detection effect
755
+ // Note: Clipboard images are handled via Alt+V keyboard shortcut
751
756
  // DELETE CHAR - Only runs if Delete Word did NOT trigger
752
757
  // Triggers on:
753
758
  // 1. Backspace or Delete key flag is present
@@ -787,6 +792,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
787
792
  setSlashAutocompleteCommands(matches);
788
793
  setSlashAutocompleteVisible(true);
789
794
  setSlashAutocompleteSelectedIndex(0);
795
+ setSlashAutocompleteScrollOffset(0);
790
796
  }
791
797
  else {
792
798
  setSlashAutocompleteVisible(false);
@@ -800,6 +806,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
800
806
  setSlashAutocompleteCommands(matches);
801
807
  setSlashAutocompleteVisible(true);
802
808
  setSlashAutocompleteSelectedIndex(0);
809
+ setSlashAutocompleteScrollOffset(0);
803
810
  }
804
811
  else {
805
812
  setSlashAutocompleteVisible(false);
@@ -813,6 +820,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
813
820
  setSlashAutocompleteCommands(matches);
814
821
  setSlashAutocompleteVisible(true);
815
822
  setSlashAutocompleteSelectedIndex(0);
823
+ setSlashAutocompleteScrollOffset(0);
816
824
  }
817
825
  else {
818
826
  setSlashAutocompleteVisible(false);
@@ -826,6 +834,35 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
826
834
  setSlashAutocompleteCommands(matches);
827
835
  setSlashAutocompleteVisible(true);
828
836
  setSlashAutocompleteSelectedIndex(0);
837
+ setSlashAutocompleteScrollOffset(0);
838
+ }
839
+ else {
840
+ setSlashAutocompleteVisible(false);
841
+ }
842
+ }
843
+ else if (newValue.startsWith('/background-task ') || newValue.startsWith('/bkg ') || newValue.startsWith('/bg-task ')) {
844
+ // Background-task subcommands
845
+ const fullQuery = newValue.slice(1);
846
+ const matches = filterCommands(fullQuery);
847
+ if (matches.length > 0) {
848
+ setSlashAutocompleteCommands(matches);
849
+ setSlashAutocompleteVisible(true);
850
+ setSlashAutocompleteSelectedIndex(0);
851
+ setSlashAutocompleteScrollOffset(0);
852
+ }
853
+ else {
854
+ setSlashAutocompleteVisible(false);
855
+ }
856
+ }
857
+ else if (newValue.startsWith('/sync ')) {
858
+ // Sync subcommands
859
+ const fullQuery = newValue.slice(1);
860
+ const matches = filterCommands(fullQuery);
861
+ if (matches.length > 0) {
862
+ setSlashAutocompleteCommands(matches);
863
+ setSlashAutocompleteVisible(true);
864
+ setSlashAutocompleteSelectedIndex(0);
865
+ setSlashAutocompleteScrollOffset(0);
829
866
  }
830
867
  else {
831
868
  setSlashAutocompleteVisible(false);
@@ -864,6 +901,16 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
864
901
  }
865
902
  else {
866
903
  // Enter: Submit
904
+ // Check if submission is allowed when disconnected
905
+ // Allowed: /exit command, OR Command Mode is active, OR detected intent is 'command'
906
+ const isExitCommand = value.trim() === '/exit';
907
+ const isCommandIntent = detectedIntent === 'command';
908
+ const isAllowedOffline = isConnected || isExitCommand || commandMode || isCommandIntent;
909
+ if (!isAllowedOffline) {
910
+ setRejectFlash(true);
911
+ setTimeout(() => setRejectFlash(false), 1000);
912
+ return;
913
+ }
867
914
  handleSubmit();
868
915
  }
869
916
  return;
@@ -1059,21 +1106,28 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
1059
1106
  pushToUndoStack();
1060
1107
  // Handle paste with newlines
1061
1108
  const cleanedInput = input.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
1062
- let newValue = value;
1063
- let newOffset = cursorOffset;
1109
+ // Use refs to get the latest value and cursor position
1110
+ // This prevents stale closure issues when Ink calls useInput multiple times during paste
1111
+ const currentValue = valueRef.current;
1112
+ const currentCursorOffset = cursorOffsetRef.current;
1113
+ let newValue = currentValue;
1114
+ let newOffset = currentCursorOffset;
1064
1115
  if (selection) {
1065
1116
  const start = Math.min(selection.start, selection.end);
1066
1117
  const end = Math.max(selection.start, selection.end);
1067
- newValue = value.slice(0, start) + cleanedInput + value.slice(end);
1118
+ newValue = currentValue.slice(0, start) + cleanedInput + currentValue.slice(end);
1068
1119
  newOffset = start + cleanedInput.length;
1069
1120
  setSelection(null);
1070
1121
  }
1071
1122
  else {
1072
- newValue = value.slice(0, cursorOffset) + cleanedInput + value.slice(cursorOffset);
1073
- newOffset = cursorOffset + cleanedInput.length;
1123
+ newValue = currentValue.slice(0, currentCursorOffset) + cleanedInput + currentValue.slice(currentCursorOffset);
1124
+ newOffset = currentCursorOffset + cleanedInput.length;
1074
1125
  }
1126
+ // Update refs immediately for subsequent paste chunks
1127
+ valueRef.current = newValue;
1128
+ cursorOffsetRef.current = newOffset;
1075
1129
  setValue(newValue);
1076
- setCursorOffset(newOffset);
1130
+ setCursorOffsetWithRef(newOffset);
1077
1131
  // Reset history/completions
1078
1132
  setHistoryIndex(-1);
1079
1133
  setCompletions([]);
@@ -1086,6 +1140,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
1086
1140
  setSlashAutocompleteCommands(matches);
1087
1141
  setSlashAutocompleteVisible(true);
1088
1142
  setSlashAutocompleteSelectedIndex(0);
1143
+ setSlashAutocompleteScrollOffset(0);
1089
1144
  }
1090
1145
  else {
1091
1146
  setSlashAutocompleteVisible(false);
@@ -1099,6 +1154,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
1099
1154
  setSlashAutocompleteCommands(matches);
1100
1155
  setSlashAutocompleteVisible(true);
1101
1156
  setSlashAutocompleteSelectedIndex(0);
1157
+ setSlashAutocompleteScrollOffset(0);
1102
1158
  }
1103
1159
  else {
1104
1160
  setSlashAutocompleteVisible(false);
@@ -1112,6 +1168,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
1112
1168
  setSlashAutocompleteCommands(matches);
1113
1169
  setSlashAutocompleteVisible(true);
1114
1170
  setSlashAutocompleteSelectedIndex(0);
1171
+ setSlashAutocompleteScrollOffset(0);
1115
1172
  }
1116
1173
  else {
1117
1174
  setSlashAutocompleteVisible(false);
@@ -1125,6 +1182,35 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
1125
1182
  setSlashAutocompleteCommands(matches);
1126
1183
  setSlashAutocompleteVisible(true);
1127
1184
  setSlashAutocompleteSelectedIndex(0);
1185
+ setSlashAutocompleteScrollOffset(0);
1186
+ }
1187
+ else {
1188
+ setSlashAutocompleteVisible(false);
1189
+ }
1190
+ }
1191
+ else if (newValue.startsWith('/background-task ') || newValue.startsWith('/bkg ') || newValue.startsWith('/bg-task ')) {
1192
+ // Background-task subcommands (when user types "/background-task ", "/bkg ", or "/bg-task ")
1193
+ const fullQuery = newValue.slice(1); // Remove leading "/", pass "background-task <subquery>" to filterCommands
1194
+ const matches = filterCommands(fullQuery);
1195
+ if (matches.length > 0) {
1196
+ setSlashAutocompleteCommands(matches);
1197
+ setSlashAutocompleteVisible(true);
1198
+ setSlashAutocompleteSelectedIndex(0);
1199
+ setSlashAutocompleteScrollOffset(0);
1200
+ }
1201
+ else {
1202
+ setSlashAutocompleteVisible(false);
1203
+ }
1204
+ }
1205
+ else if (newValue.startsWith('/sync ')) {
1206
+ // Sync subcommands (when user types "/sync ")
1207
+ const fullQuery = newValue.slice(1); // Remove leading "/", pass "sync <subquery>" to filterCommands
1208
+ const matches = filterCommands(fullQuery);
1209
+ if (matches.length > 0) {
1210
+ setSlashAutocompleteCommands(matches);
1211
+ setSlashAutocompleteVisible(true);
1212
+ setSlashAutocompleteSelectedIndex(0);
1213
+ setSlashAutocompleteScrollOffset(0);
1128
1214
  }
1129
1215
  else {
1130
1216
  setSlashAutocompleteVisible(false);
@@ -1374,17 +1460,6 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
1374
1460
  if (isInConfirmedFileTag || isInActiveFileTag) {
1375
1461
  return React.createElement(Text, { key: charIdx, color: "#00ccff", bold: true }, char);
1376
1462
  }
1377
- // Check if this character is part of a validated #image command
1378
- let isInValidatedImage = false;
1379
- for (const pos of validatedImagePositions) {
1380
- if (absPos >= pos.start && absPos < pos.end) {
1381
- isInValidatedImage = true;
1382
- break;
1383
- }
1384
- }
1385
- if (isInValidatedImage) {
1386
- return React.createElement(Text, { key: charIdx, color: "#ff69b4", bold: true }, char);
1387
- }
1388
1463
  return React.createElement(Text, { key: charIdx }, char);
1389
1464
  });
1390
1465
  // Handle cursor at end of line
@@ -1406,7 +1481,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
1406
1481
  }),
1407
1482
  endLine < totalVisualLines && React.createElement(Text, { color: "gray" }, "\u2193 ...")));
1408
1483
  };
1409
- return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: rejectFlash ? "#ff3366" : (commandMode ? "#00cc66" : "#257aa5ff"), paddingX: 1, paddingY: 0, width: "100%" },
1484
+ return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: rejectFlash ? "#ff3366" : (backgroundMode ? "#9966ff" : (commandMode ? "#00cc66" : "#257aa5ff")), paddingX: 1, paddingY: 0, width: "100%" },
1410
1485
  React.createElement(Box, { marginY: 1, justifyContent: "space-between", width: "100%" },
1411
1486
  React.createElement(Box, null,
1412
1487
  subshellContext && subshellContext.type !== 'local' && (React.createElement(Breadcrumbs, { context: subshellContext })),
@@ -1421,19 +1496,26 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
1421
1496
  isAutoMode ? (React.createElement(Text, null,
1422
1497
  React.createElement(Text, { color: "#00ccff", bold: true }, "Auto ["),
1423
1498
  detectedIntent === 'command' ? (React.createElement(Text, { color: "#00cc66", bold: true }, "Terminal")) : (React.createElement(Text, { color: "#00ccff" }, "Agent")),
1424
- React.createElement(Text, { color: "#00ccff", bold: true }, "]"))) : commandMode ? (React.createElement(Text, { color: "#00cc66", bold: true }, "Terminal")) : planMode ? (React.createElement(Text, { color: "#ffaa00", bold: true }, "Plan")) : (React.createElement(Text, { color: "#00ccff" }, "Agent"))))),
1499
+ React.createElement(Text, { color: "#00ccff", bold: true }, "]"))) : backgroundMode ? (React.createElement(Text, { color: "#9966ff", bold: true }, "Background")) : commandMode ? (React.createElement(Text, { color: "#00cc66", bold: true }, "Terminal")) : planMode ? (React.createElement(Text, { color: "#ffaa00", bold: true }, "Plan")) : (React.createElement(Text, { color: "#00ccff" }, "Agent"))))),
1500
+ confirmedClipboardImages.length > 0 && (React.createElement(Box, { marginBottom: 1, flexDirection: "row", flexWrap: "wrap", gap: 1 }, confirmedClipboardImages.map((img, index) => (React.createElement(Box, { key: img.id, borderStyle: "round", borderColor: "#ff69b4", paddingX: 1 },
1501
+ React.createElement(Text, { color: "#ff69b4", bold: true },
1502
+ "image_",
1503
+ index + 1)))))),
1425
1504
  React.createElement(Box, { flexDirection: "row", width: "100%" },
1426
1505
  React.createElement(Text, { color: "#666666" }, "> "),
1427
1506
  renderInput()),
1428
1507
  React.createElement(Box, { marginY: 1, justifyContent: "space-between", width: "100%" },
1429
- React.createElement(Text, { color: "#666666", dimColor: true }, isAutoMode ? ('Ctrl+D to switch modes • Ctrl+T to auto-approve') : commandMode ? ('Ctrl+D to switch modes • Tab for autocomplete') : ('Ctrl+D to switch modes • Ctrl+T to auto-approve • Shift+Enter for new line')),
1508
+ React.createElement(Text, { color: "#666666", dimColor: true }, isAutoMode ? ('Ctrl+D to switch modes • Ctrl+T to auto-approve') : backgroundMode ? ('Ctrl+D to switch modes • Commands run in background') : commandMode ? ('Ctrl+D to switch modes • Tab for autocomplete') : ('Ctrl+D to switch modes • Ctrl+T to auto-approve • Shift+Enter for new line')),
1430
1509
  React.createElement(Box, { gap: 1 },
1431
- !commandMode && planMode && (React.createElement(Text, { color: "#ffaa00", bold: true }, "[PLANNING]")),
1510
+ backgroundTaskCount > 0 && (React.createElement(Text, { color: "#9966ff", bold: true },
1511
+ "[bkg tasks: ",
1512
+ backgroundTaskCount,
1513
+ "]")),
1514
+ !commandMode && !backgroundMode && planMode && (React.createElement(Text, { color: "#ffaa00", bold: true }, "[PLANNING]")),
1432
1515
  !commandMode && autoAcceptMode ? (React.createElement(Text, { color: "#00cc66", bold: true }, "[AUTO-ACCEPT: ON]")) : !commandMode ? (React.createElement(Text, { color: "#666666", dimColor: true }, "[AUTO-ACCEPT: OFF]")) : null,
1433
1516
  !commandMode && (React.createElement(Box, { marginLeft: 1 },
1434
1517
  React.createElement(ContextWindowIndicator, { currentTokens: currentTokens, maxTokens: maxTokens }))))),
1435
- slashAutocompleteVisible && (React.createElement(SlashCommandAutocomplete, { commands: slashAutocompleteCommands, selectedIndex: slashAutocompleteSelectedIndex })),
1436
- fileTagAutocompleteVisible && (React.createElement(FileTagAutocomplete, { files: fileTagSuggestions, selectedIndex: fileTagSelectedIndex })),
1437
- clipboardAutocompleteVisible && (React.createElement(ClipboardImageAutocomplete, { images: clipboardImages, selectedIndex: clipboardSelectedIndex, isLoading: clipboardLoading }))));
1518
+ slashAutocompleteVisible && slashMaxVisibleItems > 0 && (React.createElement(SlashCommandAutocomplete, { commands: slashAutocompleteCommands, selectedIndex: slashAutocompleteSelectedIndex, maxVisibleItems: slashMaxVisibleItems, scrollOffset: slashAutocompleteScrollOffset })),
1519
+ fileTagAutocompleteVisible && (React.createElement(FileTagAutocomplete, { files: fileTagSuggestions, selectedIndex: fileTagSelectedIndex }))));
1438
1520
  });
1439
1521
  //# sourceMappingURL=InputBox.js.map