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,7 +2,7 @@ import React, { useState, useCallback } from 'react';
2
2
  import { Box, Text, useApp, useInput, Static } from 'ink';
3
3
  import SelectInput from 'ink-select-input';
4
4
  import TextInput from 'ink-text-input';
5
- import * as fs from 'fs';
5
+ import { quickLog } from '../../utils/conversation-logger.js';
6
6
  import { WelcomeBanner } from './WelcomeBanner.js';
7
7
  import { InputBox } from './InputBox.js';
8
8
  import { MessageDisplay, countImagesInMessage } from './MessageDisplay.js';
@@ -23,6 +23,8 @@ import { DetailedPlanReviewScreen } from './DetailedPlanReviewScreen.js';
23
23
  import { TaskCompletedMessage } from './TaskCompletedMessage.js';
24
24
  import { PlanAcceptedMessage } from './PlanAcceptedMessage.js';
25
25
  import { processTerminalOutput } from '../../utils/terminal-output.js';
26
+ import { BackgroundTaskManager } from '../../services/background-task-manager.js';
27
+ import { useConnectivity } from '../../hooks/useConnectivity.js';
26
28
  import { apiClient } from '../../services/api-client.js';
27
29
  import { conversationManager } from '../../services/conversation-manager.js';
28
30
  import { logDebug, logError } from '../../utils/logger.js';
@@ -135,29 +137,30 @@ const RenameInputScreen = ({ currentTitle, onRename, onCancel }) => {
135
137
  React.createElement(Box, { marginTop: 1 },
136
138
  React.createElement(Text, { dimColor: true }, "Press Enter to save, ESC to cancel"))));
137
139
  };
138
- export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode, onResponseReceived, onDirectMessage, onResponseStream, onThoughtStream, onThoughtComplete, onPickerSetup, onPickerSelection, onToolExecutionUpdate, onToolApprovalRequest, onToolStreamingOutput, onPlanModeChange, onPlanApprovalRequest, onPlanCreated, onTaskCompleted, onCommandModeChange, onToggleCommandMode, onCwdChange, onModelChange, onSubshellContextChange, onPasswordRequest, onShellInput, onShellSignal, onKillProcess, onInteractiveEditorMode, onConnectionStatusUpdate, onChatPickerSetup, onChatPickerSelection, onChatDeletePickerSetup, onChatDeletePickerSelection, onChatListSetup, onChatRenamePickerSetup, onChatRename, onRestoreMessagesSetup, onUIMessageHistoryUpdate }) => {
140
+ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode, onResponseReceived, onDirectMessage, onResponseStream, onThoughtStream, onThoughtComplete, onPickerSetup, onPickerSelection, onToolExecutionUpdate, onToolApprovalRequest, onToolStreamingOutput, onPlanModeChange, onPlanApprovalRequest, onPlanCreated, onTaskCompleted, onCommandModeChange, onToggleCommandMode, onBackgroundModeChange, onToggleBackgroundMode, onBackgroundTaskCountChange, onSetAutoModeSetup, onCwdChange, onModelChange, onSubshellContextChange, onPasswordRequest, onShellInput, onShellSignal, onKillProcess, onInteractiveEditorMode, onConnectionStatusUpdate, onTokenCountUpdate, onChatPickerSetup, onChatPickerSelection, onChatDeletePickerSetup, onChatDeletePickerSelection, onChatListSetup, onChatRenamePickerSetup, onChatRename, onRestoreMessagesSetup, onUIMessageHistoryUpdate, onBackgroundTaskListSetup, onBackgroundTaskSelection, onBackgroundTaskCancelSetup, onBackgroundTaskCancel, onBackgroundTaskViewSetup }) => {
139
141
  const { exit } = useApp();
140
142
  const autoAcceptRef = React.useRef(false);
143
+ const setAutoModeCallbackRef = React.useRef(null);
144
+ // Connectivity State
145
+ const isConnected = useConnectivity();
141
146
  // Helper to clear screen
142
147
  const clearScreen = useCallback(() => {
143
148
  process.stdout.write('\x1b[2J\x1b[3J\x1b[H');
144
149
  }, []);
145
- // Helper to get max tokens for a model
150
+ // Helper to get max tokens for a model (reads from cached backend config if available)
146
151
  const getMaxTokensForModel = useCallback((model) => {
147
- // Import dynamically to avoid circular dependencies
148
152
  try {
149
- const { getModelContextWindow } = require('../../config/models.js');
150
- return getModelContextWindow(model);
153
+ // Use sync function that reads from cached backend config
154
+ const { getModelContextWindowSync } = require('../../config/models.js');
155
+ return getModelContextWindowSync(model);
151
156
  }
152
157
  catch (error) {
153
- // Fallback to defaults if config loading fails
154
- if (model.includes('gemini-3'))
155
- return 2000000;
156
- if (model.includes('gemini-2.5'))
157
- return 2000000;
158
- if (model.includes('kimi'))
158
+ // Fallback defaults if import fails
159
+ if (model.toLowerCase().includes('kimi'))
159
160
  return 128000;
160
- return 2000000;
161
+ if (model.toLowerCase().includes('gemini'))
162
+ return 1000000;
163
+ return 1000000;
161
164
  }
162
165
  }, []);
163
166
  // Helper to estimate tokens (rough approximation: 1 token ≈ 4 characters)
@@ -182,6 +185,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
182
185
  currentModel: initialModel || 'gemini-2.5-flash',
183
186
  planMode: initialPlanMode || false,
184
187
  commandMode: false,
188
+ backgroundMode: false,
185
189
  currentWorkingDirectory: process.cwd(),
186
190
  showExitWarning: false,
187
191
  commandHistory: [],
@@ -198,6 +202,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
198
202
  planApprovalRequest: undefined,
199
203
  passwordRequest: undefined,
200
204
  connectionStatus: undefined,
205
+ backgroundTaskCount: 0,
201
206
  });
202
207
  // Track last terminal width to detect actual width changes
203
208
  const lastTerminalWidthRef = React.useRef(process.stdout.columns || 80);
@@ -280,24 +285,45 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
280
285
  }));
281
286
  });
282
287
  }, []);
288
+ // Fetch models config async and update maxTokens once loaded
289
+ // This ensures correct context window even on initial load
290
+ React.useEffect(() => {
291
+ const loadModelsConfig = async () => {
292
+ try {
293
+ // Import quickLog for debugging
294
+ const { quickLog } = await import('../../utils/conversation-logger.js');
295
+ quickLog(`[${new Date().toISOString()}] [App] loadModelsConfig effect starting, currentModel: ${initialModel}\n`);
296
+ const { fetchModelsConfig, getModelContextWindowSync } = await import('../../config/models.js');
297
+ quickLog(`[${new Date().toISOString()}] [App] fetchModelsConfig imported, calling...\n`);
298
+ const config = await fetchModelsConfig(); // This populates the cache
299
+ quickLog(`[${new Date().toISOString()}] [App] fetchModelsConfig returned, models count: ${config?.models?.length || 0}\n`);
300
+ // Now get maxTokens with the cached config
301
+ const newMaxTokens = getModelContextWindowSync(initialModel || 'gemini-2.5-flash');
302
+ quickLog(`[${new Date().toISOString()}] [App] Got newMaxTokens: ${newMaxTokens} for model "${initialModel}"\n`);
303
+ setState(prev => ({
304
+ ...prev,
305
+ maxTokens: newMaxTokens
306
+ }));
307
+ quickLog(`[${new Date().toISOString()}] [App] Updated state.maxTokens to ${newMaxTokens}\n`);
308
+ }
309
+ catch (error) {
310
+ // Log errors to file
311
+ try {
312
+ const { quickLog } = await import('../../utils/conversation-logger.js');
313
+ quickLog(`[${new Date().toISOString()}] [App] loadModelsConfig ERROR: ${error?.message || error}\n`);
314
+ }
315
+ catch { }
316
+ }
317
+ };
318
+ loadModelsConfig();
319
+ }, [initialModel]);
283
320
  // Keep ref in sync with state
284
321
  React.useEffect(() => {
285
322
  autoAcceptRef.current = state.autoAcceptMode;
286
323
  }, [state.autoAcceptMode]);
287
- // Update token count whenever messages change
288
- React.useEffect(() => {
289
- const allMessages = [...state.messageHistory];
290
- if (state.currentMessage) {
291
- allMessages.push(state.currentMessage);
292
- }
293
- const totalTokens = calculateTotalTokens(allMessages);
294
- if (totalTokens !== state.currentTokens) {
295
- setState(prev => ({
296
- ...prev,
297
- currentTokens: totalTokens
298
- }));
299
- }
300
- }, [state.messageHistory, state.currentMessage, calculateTotalTokens]);
324
+ // NOTE: Token count is now updated via onTokenCountUpdate callback from cli-adapter
325
+ // which tracks the actual AI conversation history including system prompt.
326
+ // The old UI-based calculation has been removed to avoid overwriting correct values.
301
327
  // Track if we're currently streaming
302
328
  const isStreamingRef = React.useRef(false);
303
329
  // Buffer content when terminal is too small for streaming display
@@ -387,7 +413,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
387
413
  onThoughtStream((thought) => {
388
414
  // Debug logging to file
389
415
  try {
390
- fs.appendFileSync('cli_frontend_logs.txt', `[${new Date().toISOString()}] [App] Received thought chunk (${thought.length} chars): ${thought.substring(0, 100)}\n`);
416
+ quickLog(`[${new Date().toISOString()}] [App] Received thought chunk (${thought.length} chars): ${thought.substring(0, 100)}\n`);
391
417
  }
392
418
  catch (e) {
393
419
  // Ignore logging errors
@@ -406,7 +432,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
406
432
  (prev.currentMessage.thinkingDuration !== undefined && !isThoughtOnlyMessage);
407
433
  if (needsNewMessage) {
408
434
  try {
409
- fs.appendFileSync('cli_frontend_logs.txt', `[${new Date().toISOString()}] [App] Creating new assistant message for thoughts (reason: ${!prev.currentMessage ? 'no current' : prev.currentMessage.role !== 'assistant' ? 'not assistant' : 'has thinkingDuration'})\n`);
435
+ quickLog(`[${new Date().toISOString()}] [App] Creating new assistant message for thoughts (reason: ${!prev.currentMessage ? 'no current' : prev.currentMessage.role !== 'assistant' ? 'not assistant' : 'has thinkingDuration'})\n`);
410
436
  }
411
437
  catch (e) {
412
438
  // Ignore logging errors
@@ -445,7 +471,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
445
471
  // (don't accumulate - the tool will handle displaying)
446
472
  if (isThoughtOnlyMessage) {
447
473
  try {
448
- fs.appendFileSync('cli_frontend_logs.txt', `[${new Date().toISOString()}] [App] Skipping thought chunk - current is thought-only message waiting for tool\n`);
474
+ quickLog(`[${new Date().toISOString()}] [App] Skipping thought chunk - current is thought-only message waiting for tool\n`);
449
475
  }
450
476
  catch (e) { }
451
477
  return prev;
@@ -457,7 +483,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
457
483
  const allLines = thoughtAccumulatorRef.current.split('\n').filter(line => line.trim());
458
484
  const last3Lines = allLines.slice(-3);
459
485
  try {
460
- fs.appendFileSync('cli_frontend_logs.txt', `[${new Date().toISOString()}] [App] Accumulated ${allLines.length} total lines, showing last 3: ${JSON.stringify(last3Lines)}\n`);
486
+ quickLog(`[${new Date().toISOString()}] [App] Accumulated ${allLines.length} total lines, showing last 3: ${JSON.stringify(last3Lines)}\n`);
461
487
  }
462
488
  catch (e) {
463
489
  // Ignore logging errors
@@ -477,7 +503,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
477
503
  React.useEffect(() => {
478
504
  onThoughtComplete((durationSeconds) => {
479
505
  try {
480
- fs.appendFileSync('cli_frontend_logs.txt', `[${new Date().toISOString()}] [App] onThoughtComplete called with ${durationSeconds}s\n`);
506
+ quickLog(`[${new Date().toISOString()}] [App] onThoughtComplete called with ${durationSeconds}s\n`);
481
507
  }
482
508
  catch (e) {
483
509
  // Ignore
@@ -488,7 +514,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
488
514
  // Only update if we have a current assistant message
489
515
  if (!prev.currentMessage || prev.currentMessage.role !== 'assistant') {
490
516
  try {
491
- fs.appendFileSync('cli_frontend_logs.txt', `[${new Date().toISOString()}] [App] No current assistant message for thought completion\n`);
517
+ quickLog(`[${new Date().toISOString()}] [App] No current assistant message for thought completion\n`);
492
518
  }
493
519
  catch (e) {
494
520
  // Ignore
@@ -496,7 +522,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
496
522
  return prev;
497
523
  }
498
524
  try {
499
- fs.appendFileSync('cli_frontend_logs.txt', `[${new Date().toISOString()}] [App] Setting thinkingDuration to ${durationSeconds}s on message ${prev.currentMessage.id}\n`);
525
+ quickLog(`[${new Date().toISOString()}] [App] Setting thinkingDuration to ${durationSeconds}s on message ${prev.currentMessage.id}\n`);
500
526
  }
501
527
  catch (e) {
502
528
  // Ignore
@@ -593,7 +619,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
593
619
  // (meaning the message was moved to history mid-stream)
594
620
  if (lastAssistant.content.length < message.length) {
595
621
  try {
596
- fs.appendFileSync('cli_frontend_logs.txt', `[${new Date().toISOString()}] [App] onResponseReceived: Updating history assistant message from ${lastAssistant.content.length} chars to ${message.length} chars\n`);
622
+ quickLog(`[${new Date().toISOString()}] [App] onResponseReceived: Updating history assistant message from ${lastAssistant.content.length} chars to ${message.length} chars\n`);
597
623
  }
598
624
  catch (e) { }
599
625
  const updatedHistory = [...prev.messageHistory];
@@ -735,6 +761,33 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
735
761
  }));
736
762
  });
737
763
  }, []); // Empty dependency array - only register once
764
+ // Set up callback for background mode change
765
+ React.useEffect(() => {
766
+ onBackgroundModeChange((backgroundMode) => {
767
+ setState(prev => ({
768
+ ...prev,
769
+ backgroundMode
770
+ }));
771
+ });
772
+ }, []); // Empty dependency array - only register once
773
+ // Set up callback for background task count change
774
+ React.useEffect(() => {
775
+ onBackgroundTaskCountChange((count) => {
776
+ setState(prev => ({
777
+ ...prev,
778
+ backgroundTaskCount: count
779
+ }));
780
+ });
781
+ }, []); // Empty dependency array - only register once
782
+ // Set up callback for setting Auto mode (used after background task starts)
783
+ React.useEffect(() => {
784
+ onSetAutoModeSetup((enabled) => {
785
+ // Call the InputBox's setAutoMode callback if registered
786
+ if (setAutoModeCallbackRef.current) {
787
+ setAutoModeCallbackRef.current(enabled);
788
+ }
789
+ });
790
+ }, []); // Empty dependency array - only register once
738
791
  // Set up callback for message restoration (when resuming a chat)
739
792
  React.useEffect(() => {
740
793
  onRestoreMessagesSetup((restoredMessages) => {
@@ -748,6 +801,121 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
748
801
  }));
749
802
  });
750
803
  }, []); // Empty dependency array - only register once
804
+ // Set up callback for background task list picker
805
+ React.useEffect(() => {
806
+ onBackgroundTaskListSetup((tasks) => {
807
+ clearScreen();
808
+ setState(prev => ({
809
+ ...prev,
810
+ screen: 'background-task-list',
811
+ backgroundTasks: tasks,
812
+ isLoading: false
813
+ }));
814
+ });
815
+ }, []); // Empty dependency array - only register once
816
+ // Set up callback for background task cancel picker
817
+ React.useEffect(() => {
818
+ onBackgroundTaskCancelSetup((tasks) => {
819
+ clearScreen();
820
+ setState(prev => ({
821
+ ...prev,
822
+ screen: 'background-task-cancel',
823
+ backgroundTasks: tasks,
824
+ isLoading: false
825
+ }));
826
+ });
827
+ }, []); // Empty dependency array - only register once
828
+ // Set up callback for viewing a background task in focus mode (live streaming shell)
829
+ React.useEffect(() => {
830
+ onBackgroundTaskViewSetup((task) => {
831
+ clearScreen();
832
+ // Set up shell state for the background task - this displays in InteractiveShell
833
+ // isFocused: true to start directly in focus mode
834
+ // isBackgroundTask: true to prevent adding to history on exit
835
+ setState(prev => ({
836
+ ...prev,
837
+ screen: 'chat',
838
+ shellState: {
839
+ command: `[Background Task] ${task.command}`,
840
+ cwd: task.cwd,
841
+ output: task.output,
842
+ isRunning: task.isRunning,
843
+ exitCode: undefined,
844
+ error: undefined,
845
+ isFocused: true, // Start directly in focus mode
846
+ remoteContext: undefined,
847
+ isBackgroundTask: true, // Flag to prevent history addition
848
+ backgroundTaskId: task.id // Store task ID for input routing
849
+ },
850
+ isLoading: false,
851
+ isAiWorking: false // Don't show AI working for background tasks
852
+ }));
853
+ // Subscribe to output updates - append to shell output
854
+ task.onOutput((chunk) => {
855
+ setState(prev => {
856
+ if (!prev.shellState || !prev.shellState.isBackgroundTask)
857
+ return prev;
858
+ return {
859
+ ...prev,
860
+ shellState: {
861
+ ...prev.shellState,
862
+ output: prev.shellState.output + chunk
863
+ }
864
+ };
865
+ });
866
+ });
867
+ // Subscribe to task completion
868
+ task.onComplete((exitCode) => {
869
+ // Debug logging
870
+ try {
871
+ quickLog(`[${new Date().toISOString()}] [App.tsx] task.onComplete called: exitCode=${exitCode}\n`);
872
+ }
873
+ catch (e) { }
874
+ setState(prev => {
875
+ // If user already exited focus mode, don't do anything
876
+ if (!prev.shellState || !prev.shellState.isBackgroundTask) {
877
+ try {
878
+ quickLog(`[${new Date().toISOString()}] [App.tsx] task.onComplete: shellState not active, ignoring\n`);
879
+ }
880
+ catch (e) { }
881
+ return prev;
882
+ }
883
+ // Task completed - mark as done and update display
884
+ try {
885
+ quickLog(`[${new Date().toISOString()}] [App.tsx] task.onComplete: marking task as complete\n`);
886
+ }
887
+ catch (e) { }
888
+ return {
889
+ ...prev,
890
+ shellState: {
891
+ ...prev.shellState,
892
+ isRunning: false,
893
+ exitCode
894
+ }
895
+ };
896
+ });
897
+ // After a brief delay, automatically return to chat
898
+ // This gives the user time to see the final output
899
+ setTimeout(() => {
900
+ try {
901
+ quickLog(`[${new Date().toISOString()}] [App.tsx] task.onComplete: clearing shellState to return to chat\n`);
902
+ }
903
+ catch (e) { }
904
+ clearScreen();
905
+ setState(prev => {
906
+ // Only clear if still showing this background task
907
+ if (prev.shellState?.isBackgroundTask) {
908
+ return {
909
+ ...prev,
910
+ shellState: undefined
911
+ };
912
+ }
913
+ return prev;
914
+ });
915
+ }, 500); // 500ms delay to see final output
916
+ });
917
+ });
918
+ }, []); // Empty dependency array - only register once
751
919
  // Sync UI message history to CLI adapter whenever it changes
752
920
  React.useEffect(() => {
753
921
  onUIMessageHistoryUpdate(state.messageHistory);
@@ -758,7 +926,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
758
926
  // Special handling for execute_command
759
927
  if (update.toolName === 'execute_command') {
760
928
  try {
761
- fs.appendFileSync('cli_frontend_logs.txt', `[${new Date().toISOString()}] [App] execute_command update: ${JSON.stringify(update)}\n`);
929
+ quickLog(`[${new Date().toISOString()}] [App] execute_command update: ${JSON.stringify(update)}\n`);
762
930
  }
763
931
  catch (e) { }
764
932
  // STARTING EXECUTION
@@ -897,10 +1065,10 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
897
1065
  setState(prev => {
898
1066
  // Debug logging to trace tool display issues
899
1067
  try {
900
- fs.appendFileSync('cli_frontend_logs.txt', `[${new Date().toISOString()}] [App] onToolExecutionUpdate setState - toolName: ${update.toolName}, status: ${update.status}\n`);
901
- fs.appendFileSync('cli_frontend_logs.txt', `[${new Date().toISOString()}] [App] Current state: screen=${prev.screen}, currentMessage.role=${prev.currentMessage?.role}, currentMessage.id=${prev.currentMessage?.id}, historyLen=${prev.messageHistory.length}\n`);
1068
+ quickLog(`[${new Date().toISOString()}] [App] onToolExecutionUpdate setState - toolName: ${update.toolName}, status: ${update.status}\n`);
1069
+ quickLog(`[${new Date().toISOString()}] [App] Current state: screen=${prev.screen}, currentMessage.role=${prev.currentMessage?.role}, currentMessage.id=${prev.currentMessage?.id}, historyLen=${prev.messageHistory.length}\n`);
902
1070
  if (prev.currentMessage?.thinkingDuration !== undefined) {
903
- fs.appendFileSync('cli_frontend_logs.txt', `[${new Date().toISOString()}] [App] Current message has thinkingDuration: ${prev.currentMessage.thinkingDuration}s\n`);
1071
+ quickLog(`[${new Date().toISOString()}] [App] Current message has thinkingDuration: ${prev.currentMessage.thinkingDuration}s\n`);
904
1072
  }
905
1073
  }
906
1074
  catch (e) { }
@@ -911,7 +1079,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
911
1079
  // Only track completed tools, skip executing tools (they need approval flow)
912
1080
  if (update.status === 'completed' || update.status === 'error') {
913
1081
  try {
914
- fs.appendFileSync('cli_frontend_logs.txt', `[${new Date().toISOString()}] [App] Tool ${update.toolName} completed while on ${prev.screen} screen, adding to history\n`);
1082
+ quickLog(`[${new Date().toISOString()}] [App] Tool ${update.toolName} completed while on ${prev.screen} screen, adding to history\n`);
915
1083
  }
916
1084
  catch (e) { }
917
1085
  // Add completed tool directly to history
@@ -945,7 +1113,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
945
1113
  // Tool completed - move to history with updated status
946
1114
  // Note: We now match ANY executing tool, not just same toolName (handles overlapping tools)
947
1115
  try {
948
- fs.appendFileSync('cli_frontend_logs.txt', `[${new Date().toISOString()}] [App] Tool ${update.toolName} completed, moving current tool to history\n`);
1116
+ quickLog(`[${new Date().toISOString()}] [App] Tool ${update.toolName} completed, moving current tool to history\n`);
949
1117
  }
950
1118
  catch (e) { }
951
1119
  const completedToolMessage = {
@@ -963,7 +1131,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
963
1131
  // This ensures thought-only messages stay as currentMessage until executing arrives
964
1132
  if (update.status === 'pending') {
965
1133
  try {
966
- fs.appendFileSync('cli_frontend_logs.txt', `[${new Date().toISOString()}] [App] Tool ${update.toolName} pending - skipping state modification, waiting for executing\n`);
1134
+ quickLog(`[${new Date().toISOString()}] [App] Tool ${update.toolName} pending - skipping state modification, waiting for executing\n`);
967
1135
  }
968
1136
  catch (e) { }
969
1137
  return prev; // Return unchanged state
@@ -982,13 +1150,13 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
982
1150
  // Carry the thinkingDuration to the tool message, don't add empty message to history
983
1151
  pendingThinkingDuration = prev.currentMessage.thinkingDuration;
984
1152
  try {
985
- fs.appendFileSync('cli_frontend_logs.txt', `[${new Date().toISOString()}] [App] Found thought-only message, saving thinkingDuration ${pendingThinkingDuration}s for tool ${update.toolName}\n`);
1153
+ quickLog(`[${new Date().toISOString()}] [App] Found thought-only message, saving thinkingDuration ${pendingThinkingDuration}s for tool ${update.toolName}\n`);
986
1154
  }
987
1155
  catch (e) { }
988
1156
  }
989
1157
  else {
990
1158
  try {
991
- fs.appendFileSync('cli_frontend_logs.txt', `[${new Date().toISOString()}] [App] Moving current message (${prev.currentMessage.role}) to history for new tool ${update.toolName}\n`);
1159
+ quickLog(`[${new Date().toISOString()}] [App] Moving current message (${prev.currentMessage.role}) to history for new tool ${update.toolName}\n`);
992
1160
  }
993
1161
  catch (e) { }
994
1162
  // Move ANY current message to history before showing tool
@@ -1010,7 +1178,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1010
1178
  }
1011
1179
  // For completed tools without a current executing one (edge case)
1012
1180
  try {
1013
- fs.appendFileSync('cli_frontend_logs.txt', `[${new Date().toISOString()}] [App] Tool ${update.toolName} completed without current executing tool, adding to history\n`);
1181
+ quickLog(`[${new Date().toISOString()}] [App] Tool ${update.toolName} completed without current executing tool, adding to history\n`);
1014
1182
  }
1015
1183
  catch (e) { }
1016
1184
  let newHistory = prev.messageHistory;
@@ -1225,10 +1393,13 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1225
1393
  }, [onCwdChange]);
1226
1394
  // Set up callback to receive model changes
1227
1395
  React.useEffect(() => {
1228
- onModelChange((modelName) => {
1396
+ onModelChange((modelName, contextWindow) => {
1397
+ // Update both currentModel AND maxTokens when model changes
1398
+ // contextWindow is passed directly from cli-adapter, no lookup needed
1229
1399
  setState(prev => ({
1230
1400
  ...prev,
1231
- currentModel: modelName
1401
+ currentModel: modelName,
1402
+ maxTokens: contextWindow
1232
1403
  }));
1233
1404
  });
1234
1405
  }, [onModelChange]);
@@ -1241,6 +1412,15 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1241
1412
  }));
1242
1413
  });
1243
1414
  }, [onSubshellContextChange]);
1415
+ // Set up callback to receive actual AI context token count from cli-adapter
1416
+ React.useEffect(() => {
1417
+ onTokenCountUpdate((tokens) => {
1418
+ setState(prev => ({
1419
+ ...prev,
1420
+ currentTokens: tokens
1421
+ }));
1422
+ });
1423
+ }, [onTokenCountUpdate]);
1244
1424
  // Set up callback for plan approval requests
1245
1425
  React.useEffect(() => {
1246
1426
  onPlanApprovalRequest(async (plan) => {
@@ -1265,14 +1445,14 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1265
1445
  // The plan will be displayed via the plan-approval screen
1266
1446
  // This callback can be used for additional notifications if needed
1267
1447
  try {
1268
- fs.appendFileSync('cli_frontend_logs.txt', `[${new Date().toISOString()}] [App] Plan created: ${plan.title} with ${plan.steps.length} tasks\n`);
1448
+ quickLog(`[${new Date().toISOString()}] [App] Plan created: ${plan.title} with ${plan.steps.length} tasks\n`);
1269
1449
  }
1270
1450
  catch (e) { }
1271
1451
  });
1272
1452
  }, [onPlanCreated]);
1273
1453
  // Set up callback for task completion
1274
1454
  React.useEffect(() => {
1275
- onTaskCompleted((task, taskNumber, totalTasks, completionNote) => {
1455
+ onTaskCompleted((task, taskNumber, totalTasks, completionNote, taskDescription) => {
1276
1456
  // Add task completion message to history
1277
1457
  setState(prev => {
1278
1458
  const taskCompletedMessage = {
@@ -1283,7 +1463,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1283
1463
  taskCompletion: {
1284
1464
  taskNumber,
1285
1465
  totalTasks,
1286
- taskDescription: task.description,
1466
+ // Use the passed taskDescription if available (for subtasks), otherwise fall back to task.description
1467
+ taskDescription: taskDescription || task.description,
1287
1468
  completionNote
1288
1469
  }
1289
1470
  };
@@ -1374,7 +1555,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1374
1555
  }
1375
1556
  else {
1376
1557
  // Fallback if no client available
1377
- console.error('SSH client not available for editor mode');
1558
+ logError('SSH client not available for editor mode', new Error('No SSH client'));
1378
1559
  setState(prev => ({ ...prev, isInteractiveEditorMode: false }));
1379
1560
  }
1380
1561
  }
@@ -1615,16 +1796,30 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1615
1796
  }
1616
1797
  else if (!isSlashCommand) {
1617
1798
  // Only add user message to display if it's not a slash command
1618
- // (slash commands will show their response only)
1799
+ // Show user message IMMEDIATELY with image breadcrumbs (uploading state)
1800
+ // Images will be uploaded in background, then AI will be called
1801
+ const hasImagesToUpload = clipboardImages && clipboardImages.length > 0;
1802
+ // Build initial content with placeholder image markers for immediate display
1803
+ let initialDisplayContent = trimmedValue;
1804
+ if (hasImagesToUpload) {
1805
+ // Add placeholder markers that will be shown immediately
1806
+ for (let i = 0; i < clipboardImages.length; i++) {
1807
+ const image = clipboardImages[i];
1808
+ // Use a placeholder format that MessageDisplay can render as breadcrumb
1809
+ // Format: [IMAGE: filename (uploading...)] - will be updated after upload
1810
+ initialDisplayContent = `${initialDisplayContent} [IMAGE: ${image.displayName} (gs://uploading)]`;
1811
+ }
1812
+ }
1813
+ // Generate a stable message ID that we can use to update the message later
1814
+ const userMessageId = `user-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
1619
1815
  setState(prev => {
1620
- // Move current message to history if exists
1621
1816
  const newHistory = prev.currentMessage
1622
1817
  ? [...prev.messageHistory, prev.currentMessage]
1623
1818
  : prev.messageHistory;
1624
1819
  const userMessage = {
1625
- id: `user-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
1820
+ id: userMessageId,
1626
1821
  role: 'user',
1627
- content: trimmedValue,
1822
+ content: initialDisplayContent,
1628
1823
  timestamp: new Date()
1629
1824
  };
1630
1825
  return {
@@ -1632,8 +1827,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1632
1827
  messageHistory: [...newHistory, userMessage],
1633
1828
  currentMessage: null,
1634
1829
  isLoading: true,
1635
- loadingMessage: 'Processing your request...',
1636
- isAiWorking: true // AI is starting to work
1830
+ loadingMessage: hasImagesToUpload ? 'Uploading image(s)...' : 'Processing your request...',
1831
+ isAiWorking: true
1637
1832
  };
1638
1833
  });
1639
1834
  }
@@ -1694,7 +1889,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1694
1889
  // Only attempt upload if we have a valid conversation ID
1695
1890
  if (conversationId && !conversationId.startsWith('temp_')) {
1696
1891
  logDebug(`[CLIPBOARD] Starting upload for ${clipboardImages.length} images with conversationId: ${conversationId}`);
1697
- // Upload each clipboard image
1892
+ // Upload each clipboard image and build the final message content
1893
+ const uploadedImageMarkers = [];
1698
1894
  for (const image of clipboardImages) {
1699
1895
  try {
1700
1896
  logDebug(`[CLIPBOARD] Uploading image: ${image.displayName}, size: ${image.sizeBytes} bytes`);
@@ -1703,19 +1899,50 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1703
1899
  // Append image reference info to message (for AI to know an image was attached)
1704
1900
  // The backend will use gcsUri for Vertex AI multimodal input
1705
1901
  if (uploadResult.gcsUri) {
1706
- // Replace #image with the actual gcsUri marker
1707
- // Case insensitive, first occurrence only
1708
- messageWithImages = messageWithImages.replace(/#image\b/i, `[IMAGE: ${uploadResult.fileName} (${uploadResult.gcsUri})]`);
1709
- logDebug(`[CLIPBOARD] Replaced #image with: [IMAGE: ${uploadResult.fileName} (${uploadResult.gcsUri})]`);
1902
+ // Build the real image marker with actual GCS URI
1903
+ messageWithImages = `${messageWithImages} [IMAGE: ${uploadResult.fileName} (${uploadResult.gcsUri})]`;
1904
+ uploadedImageMarkers.push(`[IMAGE: ${uploadResult.fileName} (${uploadResult.gcsUri})]`);
1905
+ logDebug(`[CLIPBOARD] Appended image marker: [IMAGE: ${uploadResult.fileName} (${uploadResult.gcsUri})]`);
1710
1906
  }
1711
1907
  }
1712
1908
  catch (uploadError) {
1713
1909
  // Log error but continue - image upload is optional
1714
1910
  logError('Failed to upload clipboard image', uploadError instanceof Error ? uploadError : undefined);
1715
- // Remove the #image placeholder if upload failed
1716
- messageWithImages = messageWithImages.replace(/#image\b/i, '[IMAGE UPLOAD FAILED]');
1911
+ // Add a failed marker so user knows upload failed
1912
+ uploadedImageMarkers.push(`[IMAGE: ${image.displayName} (upload failed)]`);
1717
1913
  }
1718
1914
  }
1915
+ // Update the user message in history with actual GCS URIs
1916
+ // Replace placeholder markers (gs://uploading) with real ones
1917
+ setState(prev => {
1918
+ const updatedHistory = prev.messageHistory.map(msg => {
1919
+ // Find the user message we just added (it has placeholder markers)
1920
+ if (msg.role === 'user' && msg.content.includes('(gs://uploading)')) {
1921
+ // Build new content: original text + real image markers
1922
+ let newContent = trimmedValue;
1923
+ for (const marker of uploadedImageMarkers) {
1924
+ newContent = `${newContent} ${marker}`;
1925
+ }
1926
+ return { ...msg, content: newContent };
1927
+ }
1928
+ return msg;
1929
+ });
1930
+ return { ...prev, messageHistory: updatedHistory };
1931
+ });
1932
+ }
1933
+ else {
1934
+ // No valid conversation ID - update message to show upload failed
1935
+ setState(prev => {
1936
+ const updatedHistory = prev.messageHistory.map(msg => {
1937
+ if (msg.role === 'user' && msg.content.includes('(gs://uploading)')) {
1938
+ // Replace uploading placeholders with failed markers
1939
+ const newContent = msg.content.replace(/\(gs:\/\/uploading\)/g, '(upload failed - no conversation)');
1940
+ return { ...msg, content: newContent };
1941
+ }
1942
+ return msg;
1943
+ });
1944
+ return { ...prev, messageHistory: updatedHistory };
1945
+ });
1719
1946
  } // End: if (conversationId && !startsWith temp_)
1720
1947
  } // End: if (clipboardImages && clipboardImages.length > 0)
1721
1948
  await onMessage(messageWithImages);
@@ -1769,11 +1996,34 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1769
1996
  state.screen === 'chat' && (React.createElement(React.Fragment, null,
1770
1997
  !state.shellState?.isFocused && (React.createElement(MessageList, { history: state.messageHistory, current: state.currentMessage, showBanner: true })),
1771
1998
  state.shellState && (getTerminalDimensions().shouldEnableStreaming || !state.shellState.isRunning) && (React.createElement(InteractiveShell, { command: state.shellState.command, cwd: state.shellState.cwd, isRunning: state.shellState.isRunning, output: state.shellState.output, exitCode: state.shellState.exitCode, error: state.shellState.error, isFocused: state.shellState.isFocused, onResize: state.shellState.onResize, remoteContext: state.shellState.remoteContext, onInput: (input) => {
1999
+ // Debug logging
2000
+ try {
2001
+ quickLog(`[${new Date().toISOString()}] [App.tsx onInput] input: ${JSON.stringify(input)}, isBackgroundTask: ${state.shellState?.isBackgroundTask}, backgroundTaskId: ${state.shellState?.backgroundTaskId}\n`);
2002
+ }
2003
+ catch (e) { }
2004
+ // Check if this is a background task
2005
+ if (state.shellState?.isBackgroundTask && state.shellState?.backgroundTaskId) {
2006
+ // Route input to background task
2007
+ BackgroundTaskManager.sendInput(state.shellState.backgroundTaskId, input);
2008
+ return;
2009
+ }
1772
2010
  // PTY MODE: Send everything immediately
1773
2011
  onShellInput(input);
1774
2012
  }, onFocusChange: (focused) => {
1775
2013
  // Clear screen when entering/exiting focus mode
1776
2014
  clearScreen();
2015
+ // Special handling for background tasks
2016
+ if (!focused && state.shellState?.isBackgroundTask) {
2017
+ // Exiting focus mode for a background task:
2018
+ // Just clear shellState and return to chat, no history message
2019
+ // The task continues running in background
2020
+ setState(prev => ({
2021
+ ...prev,
2022
+ shellState: undefined // Clear completely, no preview
2023
+ }));
2024
+ return;
2025
+ }
2026
+ // Normal shell focus change
1777
2027
  setState(prev => ({
1778
2028
  ...prev,
1779
2029
  shellState: prev.shellState ? {
@@ -1782,6 +2032,15 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1782
2032
  } : undefined
1783
2033
  }));
1784
2034
  }, onSignal: (signal) => {
2035
+ // Check if this is a background task
2036
+ if (state.shellState?.isBackgroundTask && state.shellState?.backgroundTaskId) {
2037
+ // Route signal to background task
2038
+ const nodeSignal = signal === 'SIGINT' ? 'SIGINT' :
2039
+ signal === 'SIGTERM' ? 'SIGTERM' :
2040
+ signal === 'SIGKILL' ? 'SIGKILL' : 'SIGINT';
2041
+ BackgroundTaskManager.sendSignal(state.shellState.backgroundTaskId, nodeSignal);
2042
+ return;
2043
+ }
1785
2044
  onShellSignal(signal);
1786
2045
  } })),
1787
2046
  state.isAiWorking && !state.shellState && !state.approvalRequest &&
@@ -1797,9 +2056,13 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1797
2056
  // Clear preserved input on submit
1798
2057
  preservedInputTextRef.current = '';
1799
2058
  handleSubmit(value, clipboardImages);
1800
- }, autoAcceptMode: state.autoAcceptMode, model: state.currentModel, planMode: state.planMode, commandMode: state.commandMode, currentWorkingDirectory: state.currentWorkingDirectory, commandHistory: state.commandHistory, onToggleAutoAccept: handleToggleAutoAccept, onToggleCommandMode: onToggleCommandMode, isActive: true, subshellContext: state.subshellContext, currentTokens: state.currentTokens, maxTokens: state.maxTokens, isShellRunning: state.shellState?.isRunning, initialValue: preservedInputTextRef.current, onValueChange: handleInputValueChange }))),
2059
+ }, autoAcceptMode: state.autoAcceptMode, model: state.currentModel, planMode: state.planMode, commandMode: state.commandMode, backgroundMode: state.backgroundMode, currentWorkingDirectory: state.currentWorkingDirectory, commandHistory: state.commandHistory, onToggleAutoAccept: handleToggleAutoAccept, onToggleCommandMode: onToggleCommandMode, onToggleBackgroundMode: onToggleBackgroundMode, isActive: true, subshellContext: state.subshellContext, currentTokens: state.currentTokens, maxTokens: state.maxTokens, isShellRunning: state.shellState?.isRunning, backgroundTaskCount: state.backgroundTaskCount, initialValue: preservedInputTextRef.current, onValueChange: handleInputValueChange, onSetAutoModeSetup: (callback) => {
2060
+ setAutoModeCallbackRef.current = callback;
2061
+ } }))),
1801
2062
  state.showExitWarning && (React.createElement(Box, { marginTop: 1 },
1802
- React.createElement(Text, { color: "#ffaa00", bold: true }, "\u26A0\uFE0F Press Ctrl+C again to exit"))))),
2063
+ React.createElement(Text, { color: "#ffaa00", bold: true }, "\u26A0\uFE0F Press Ctrl+C again to exit"))),
2064
+ !isConnected && (React.createElement(Box, { marginTop: 0, marginLeft: state.showExitWarning ? 2 : 0 },
2065
+ React.createElement(Text, { color: "red", bold: true }, "\u26A0\uFE0F No internet connection"))))),
1803
2066
  state.screen === 'approval' && state.approvalRequest && (React.createElement(ApprovalSection, { key: `approval-${state.approvalRequest.message}`, approvalRequest: state.approvalRequest, onApprove: (approved) => {
1804
2067
  const resolve = state.approvalRequest?.resolve;
1805
2068
  if (resolve) {
@@ -2096,6 +2359,79 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
2096
2359
  ...prev,
2097
2360
  screen: 'chat'
2098
2361
  }));
2099
- } })))));
2362
+ } }))),
2363
+ state.screen === 'background-task-list' && state.backgroundTasks && (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "#9966ff", paddingX: 1 },
2364
+ React.createElement(Text, { color: "#9966ff", bold: true }, "\uD83D\uDD04 Running Background Tasks"),
2365
+ React.createElement(Text, { dimColor: true }, "Select a task to view its output in focus mode"),
2366
+ React.createElement(Box, { marginTop: 1 },
2367
+ React.createElement(SelectInput, { items: state.backgroundTasks.map((task) => {
2368
+ const durationSec = Math.round(task.durationMs / 1000);
2369
+ return {
2370
+ label: task.command.length > 50 ? task.command.slice(0, 47) + '...' : task.command,
2371
+ value: task.id,
2372
+ cwd: task.cwd,
2373
+ duration: `${durationSec}s`,
2374
+ isRunning: task.isRunning,
2375
+ outputPreview: task.outputPreview.length > 60 ? task.outputPreview.slice(0, 57) + '...' : task.outputPreview
2376
+ };
2377
+ }), itemComponent: ({ isSelected, label, cwd, duration, isRunning, outputPreview }) => (React.createElement(Box, { flexDirection: "column" },
2378
+ React.createElement(Box, null,
2379
+ React.createElement(Text, { color: isSelected ? '#9966ff' : 'white', bold: isSelected },
2380
+ isSelected ? '> ' : ' ',
2381
+ label),
2382
+ React.createElement(Text, { color: isRunning ? '#00cc66' : '#ffaa00' },
2383
+ " [",
2384
+ isRunning ? 'running' : 'done',
2385
+ "]"),
2386
+ React.createElement(Text, { color: "gray" },
2387
+ " \u23F1\uFE0F ",
2388
+ duration)),
2389
+ React.createElement(Box, { paddingLeft: 4 },
2390
+ React.createElement(Text, { color: "gray" },
2391
+ "\uD83D\uDCC1 ",
2392
+ cwd)),
2393
+ outputPreview && (React.createElement(Box, { paddingLeft: 4 },
2394
+ React.createElement(Text, { color: "gray", dimColor: true },
2395
+ "\u2514\u2500 ",
2396
+ outputPreview))))), onSelect: (item) => {
2397
+ setState(prev => ({ ...prev, screen: 'chat', isLoading: false }));
2398
+ onBackgroundTaskSelection(item.value);
2399
+ } })),
2400
+ React.createElement(Box, { marginTop: 1 },
2401
+ React.createElement(Text, { dimColor: true }, "Press ESC to return to chat")))),
2402
+ state.screen === 'background-task-cancel' && state.backgroundTasks && (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "#ff6666", paddingX: 1 },
2403
+ React.createElement(Text, { color: "#ff6666", bold: true }, "\uD83D\uDED1 Cancel a Background Task"),
2404
+ React.createElement(Text, { dimColor: true }, "Select a task to terminate it"),
2405
+ React.createElement(Box, { marginTop: 1 },
2406
+ React.createElement(SelectInput, { items: state.backgroundTasks.map((task) => {
2407
+ const durationSec = Math.round(task.durationMs / 1000);
2408
+ return {
2409
+ label: task.command.length > 50 ? task.command.slice(0, 47) + '...' : task.command,
2410
+ value: task.id,
2411
+ cwd: task.cwd,
2412
+ duration: `${durationSec}s`,
2413
+ isRunning: task.isRunning
2414
+ };
2415
+ }), itemComponent: ({ isSelected, label, cwd, duration, isRunning }) => (React.createElement(Box, { flexDirection: "column" },
2416
+ React.createElement(Box, null,
2417
+ React.createElement(Text, { color: isSelected ? '#ff6666' : 'white', bold: isSelected },
2418
+ isSelected ? '> ' : ' ',
2419
+ label),
2420
+ React.createElement(Text, { color: isRunning ? '#00cc66' : '#ffaa00' },
2421
+ " [",
2422
+ isRunning ? 'running' : 'done',
2423
+ "]"),
2424
+ React.createElement(Text, { color: "gray" },
2425
+ " \u23F1\uFE0F ",
2426
+ duration)),
2427
+ React.createElement(Box, { paddingLeft: 4 },
2428
+ React.createElement(Text, { color: "gray" },
2429
+ "\uD83D\uDCC1 ",
2430
+ cwd)))), onSelect: (item) => {
2431
+ setState(prev => ({ ...prev, screen: 'chat', isLoading: false }));
2432
+ onBackgroundTaskCancel(item.value);
2433
+ } })),
2434
+ React.createElement(Box, { marginTop: 1 },
2435
+ React.createElement(Text, { dimColor: true }, "Press ESC to return to chat"))))));
2100
2436
  };
2101
2437
  //# sourceMappingURL=App.js.map