centaurus-cli 2.9.1 → 2.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-adapter.d.ts +76 -3
- package/dist/cli-adapter.d.ts.map +1 -1
- package/dist/cli-adapter.js +593 -230
- package/dist/cli-adapter.js.map +1 -1
- package/dist/config/mcp-config-manager.d.ts +21 -0
- package/dist/config/mcp-config-manager.d.ts.map +1 -1
- package/dist/config/mcp-config-manager.js +184 -1
- package/dist/config/mcp-config-manager.js.map +1 -1
- package/dist/config/models.d.ts +1 -0
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +9 -2
- package/dist/config/models.js.map +1 -1
- package/dist/config/slash-commands.d.ts +3 -0
- package/dist/config/slash-commands.d.ts.map +1 -1
- package/dist/config/slash-commands.js +39 -4
- package/dist/config/slash-commands.js.map +1 -1
- package/dist/config/types.d.ts +2 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +1 -0
- package/dist/config/types.js.map +1 -1
- package/dist/index.js +60 -11
- package/dist/index.js.map +1 -1
- package/dist/mcp/mcp-command-handler.d.ts +34 -3
- package/dist/mcp/mcp-command-handler.d.ts.map +1 -1
- package/dist/mcp/mcp-command-handler.js +171 -83
- package/dist/mcp/mcp-command-handler.js.map +1 -1
- package/dist/mcp/mcp-server-manager.d.ts.map +1 -1
- package/dist/mcp/mcp-server-manager.js +9 -23
- package/dist/mcp/mcp-server-manager.js.map +1 -1
- package/dist/mcp/mcp-tool-wrapper.d.ts.map +1 -1
- package/dist/mcp/mcp-tool-wrapper.js +42 -5
- package/dist/mcp/mcp-tool-wrapper.js.map +1 -1
- package/dist/services/ai-autocomplete-agent.d.ts +39 -0
- package/dist/services/ai-autocomplete-agent.d.ts.map +1 -0
- package/dist/services/ai-autocomplete-agent.js +189 -0
- package/dist/services/ai-autocomplete-agent.js.map +1 -0
- package/dist/services/ai-service-client.d.ts +25 -0
- package/dist/services/ai-service-client.d.ts.map +1 -1
- package/dist/services/ai-service-client.js +162 -1
- package/dist/services/ai-service-client.js.map +1 -1
- package/dist/services/api-client.d.ts +9 -0
- package/dist/services/api-client.d.ts.map +1 -1
- package/dist/services/api-client.js +25 -0
- package/dist/services/api-client.js.map +1 -1
- package/dist/services/auth-handler.js +1 -1
- package/dist/services/auth-handler.js.map +1 -1
- package/dist/services/input-detection-agent.d.ts +40 -0
- package/dist/services/input-detection-agent.d.ts.map +1 -0
- package/dist/services/input-detection-agent.js +213 -0
- package/dist/services/input-detection-agent.js.map +1 -0
- package/dist/services/input-requirement-detector.d.ts +28 -0
- package/dist/services/input-requirement-detector.d.ts.map +1 -0
- package/dist/services/input-requirement-detector.js +203 -0
- package/dist/services/input-requirement-detector.js.map +1 -0
- package/dist/services/local-chat-storage.d.ts +21 -0
- package/dist/services/local-chat-storage.d.ts.map +1 -1
- package/dist/services/local-chat-storage.js +138 -43
- package/dist/services/local-chat-storage.js.map +1 -1
- package/dist/services/monitored-shell-manager.d.ts +120 -0
- package/dist/services/monitored-shell-manager.d.ts.map +1 -0
- package/dist/services/monitored-shell-manager.js +239 -0
- package/dist/services/monitored-shell-manager.js.map +1 -0
- package/dist/services/ollama-service.d.ts +197 -0
- package/dist/services/ollama-service.d.ts.map +1 -0
- package/dist/services/ollama-service.js +324 -0
- package/dist/services/ollama-service.js.map +1 -0
- package/dist/services/shell-input-agent.d.ts +89 -0
- package/dist/services/shell-input-agent.d.ts.map +1 -0
- package/dist/services/shell-input-agent.js +361 -0
- package/dist/services/shell-input-agent.js.map +1 -0
- package/dist/services/sub-agent-manager.d.ts +139 -0
- package/dist/services/sub-agent-manager.d.ts.map +1 -0
- package/dist/services/sub-agent-manager.js +517 -0
- package/dist/services/sub-agent-manager.js.map +1 -0
- package/dist/tools/background-command.d.ts.map +1 -1
- package/dist/tools/background-command.js +33 -13
- package/dist/tools/background-command.js.map +1 -1
- package/dist/tools/command.d.ts.map +1 -1
- package/dist/tools/command.js +64 -1
- package/dist/tools/command.js.map +1 -1
- package/dist/tools/file-ops.d.ts.map +1 -1
- package/dist/tools/file-ops.js +33 -19
- package/dist/tools/file-ops.js.map +1 -1
- package/dist/tools/get-diff.js +1 -1
- package/dist/tools/get-diff.js.map +1 -1
- package/dist/tools/grep-search.d.ts.map +1 -1
- package/dist/tools/grep-search.js +41 -15
- package/dist/tools/grep-search.js.map +1 -1
- package/dist/tools/plan-mode.js +3 -3
- package/dist/tools/plan-mode.js.map +1 -1
- package/dist/tools/registry.js +1 -1
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/sub-agent.d.ts +9 -0
- package/dist/tools/sub-agent.d.ts.map +1 -0
- package/dist/tools/sub-agent.js +232 -0
- package/dist/tools/sub-agent.js.map +1 -0
- package/dist/tools/task-complete.d.ts.map +1 -1
- package/dist/tools/task-complete.js +14 -26
- package/dist/tools/task-complete.js.map +1 -1
- package/dist/ui/components/App.d.ts +45 -2
- package/dist/ui/components/App.d.ts.map +1 -1
- package/dist/ui/components/App.js +605 -96
- package/dist/ui/components/App.js.map +1 -1
- package/dist/ui/components/CircularSelectInput.d.ts +24 -0
- package/dist/ui/components/CircularSelectInput.d.ts.map +1 -0
- package/dist/ui/components/CircularSelectInput.js +71 -0
- package/dist/ui/components/CircularSelectInput.js.map +1 -0
- package/dist/ui/components/ErrorBoundary.d.ts +3 -2
- package/dist/ui/components/ErrorBoundary.d.ts.map +1 -1
- package/dist/ui/components/ErrorBoundary.js +29 -1
- package/dist/ui/components/ErrorBoundary.js.map +1 -1
- package/dist/ui/components/InputBox.d.ts +4 -0
- package/dist/ui/components/InputBox.d.ts.map +1 -1
- package/dist/ui/components/InputBox.js +343 -21
- package/dist/ui/components/InputBox.js.map +1 -1
- package/dist/ui/components/InteractiveShell.d.ts +6 -0
- package/dist/ui/components/InteractiveShell.d.ts.map +1 -1
- package/dist/ui/components/InteractiveShell.js +57 -6
- package/dist/ui/components/InteractiveShell.js.map +1 -1
- package/dist/ui/components/MCPAddScreen.d.ts +13 -0
- package/dist/ui/components/MCPAddScreen.d.ts.map +1 -0
- package/dist/ui/components/MCPAddScreen.js +54 -0
- package/dist/ui/components/MCPAddScreen.js.map +1 -0
- package/dist/ui/components/MCPListScreen.d.ts +17 -0
- package/dist/ui/components/MCPListScreen.d.ts.map +1 -0
- package/dist/ui/components/MCPListScreen.js +50 -0
- package/dist/ui/components/MCPListScreen.js.map +1 -0
- package/dist/ui/components/MCPServerListScreen.d.ts +16 -0
- package/dist/ui/components/MCPServerListScreen.d.ts.map +1 -0
- package/dist/ui/components/MCPServerListScreen.js +59 -0
- package/dist/ui/components/MCPServerListScreen.js.map +1 -0
- package/dist/ui/components/MonitorModeAIPanel.d.ts +23 -0
- package/dist/ui/components/MonitorModeAIPanel.d.ts.map +1 -0
- package/dist/ui/components/MonitorModeAIPanel.js +69 -0
- package/dist/ui/components/MonitorModeAIPanel.js.map +1 -0
- package/dist/ui/components/MultiLineInput.d.ts +13 -0
- package/dist/ui/components/MultiLineInput.d.ts.map +1 -0
- package/dist/ui/components/MultiLineInput.js +289 -0
- package/dist/ui/components/MultiLineInput.js.map +1 -0
- package/dist/ui/components/StatusBar.d.ts +2 -0
- package/dist/ui/components/StatusBar.d.ts.map +1 -1
- package/dist/ui/components/StatusBar.js +33 -2
- package/dist/ui/components/StatusBar.js.map +1 -1
- package/dist/ui/components/ToolExecutionMessage.d.ts.map +1 -1
- package/dist/ui/components/ToolExecutionMessage.js +231 -13
- package/dist/ui/components/ToolExecutionMessage.js.map +1 -1
- package/dist/ui/components/VersionUpdatePrompt.d.ts.map +1 -1
- package/dist/ui/components/VersionUpdatePrompt.js +3 -2
- package/dist/ui/components/VersionUpdatePrompt.js.map +1 -1
- package/dist/utils/command-history.d.ts +12 -2
- package/dist/utils/command-history.d.ts.map +1 -1
- package/dist/utils/command-history.js +57 -13
- package/dist/utils/command-history.js.map +1 -1
- package/dist/utils/input-classifier.js +1 -1
- package/dist/utils/input-classifier.js.map +1 -1
- package/package.json +2 -1
|
@@ -1,15 +1,43 @@
|
|
|
1
1
|
import React, { Component } from 'react';
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import { logError } from '../../utils/logger.js';
|
|
4
|
+
import { quickLog } from '../../utils/conversation-logger.js';
|
|
5
|
+
// Maximum recovery attempts before showing error screen
|
|
6
|
+
const MAX_RECOVERY_ATTEMPTS = 3;
|
|
4
7
|
export class ErrorBoundary extends Component {
|
|
5
8
|
constructor(props) {
|
|
6
9
|
super(props);
|
|
7
|
-
this.state = { hasError: false };
|
|
10
|
+
this.state = { hasError: false, recoveryAttempts: 0 };
|
|
8
11
|
}
|
|
9
12
|
static getDerivedStateFromError(error) {
|
|
13
|
+
// Check if this is a recoverable WASM error
|
|
14
|
+
if (error.name === 'RuntimeError' && error.message?.includes('memory access out of bounds')) {
|
|
15
|
+
// Don't set hasError for WASM memory errors - they're recoverable
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
10
18
|
return { hasError: true, error };
|
|
11
19
|
}
|
|
12
20
|
componentDidCatch(error, errorInfo) {
|
|
21
|
+
// Check if this is a WASM memory error - these are recoverable
|
|
22
|
+
if (error.name === 'RuntimeError' && error.message?.includes('memory access out of bounds')) {
|
|
23
|
+
quickLog(`[${new Date().toISOString()}] [WASM] ErrorBoundary recovering: ${error.message}\n`);
|
|
24
|
+
// Increment recovery attempts
|
|
25
|
+
const newAttempts = this.state.recoveryAttempts + 1;
|
|
26
|
+
if (newAttempts > MAX_RECOVERY_ATTEMPTS) {
|
|
27
|
+
// Too many errors, show error screen
|
|
28
|
+
quickLog(`[${new Date().toISOString()}] [WASM] Too many errors, showing error screen\n`);
|
|
29
|
+
this.setState({ hasError: true, error, recoveryAttempts: 0 });
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
// Try to recover by re-rendering after a brief delay
|
|
33
|
+
this.setState({ recoveryAttempts: newAttempts });
|
|
34
|
+
setTimeout(() => {
|
|
35
|
+
this.forceUpdate();
|
|
36
|
+
}, 100);
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// Log non-recoverable errors
|
|
13
41
|
logError('Centaurus CLI Error', error);
|
|
14
42
|
}
|
|
15
43
|
render() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ErrorBoundary.js","sourceRoot":"","sources":["../../../src/ui/components/ErrorBoundary.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,SAAS,EAAa,MAAM,OAAO,CAAC;AACpD,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"ErrorBoundary.js","sourceRoot":"","sources":["../../../src/ui/components/ErrorBoundary.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,SAAS,EAAa,MAAM,OAAO,CAAC;AACpD,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAC;AAY9D,wDAAwD;AACxD,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAEhC,MAAM,OAAO,aAAc,SAAQ,SAAuB;IACxD,YAAY,KAAY;QACtB,KAAK,CAAC,KAAK,CAAC,CAAC;QACb,IAAI,CAAC,KAAK,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,EAAE,CAAC;IACxD,CAAC;IAED,MAAM,CAAC,wBAAwB,CAAC,KAAY;QAC1C,4CAA4C;QAC5C,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,IAAI,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,6BAA6B,CAAC,EAAE,CAAC;YAC5F,kEAAkE;YAClE,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACnC,CAAC;IAED,iBAAiB,CAAC,KAAY,EAAE,SAAc;QAC5C,+DAA+D;QAC/D,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,IAAI,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,6BAA6B,CAAC,EAAE,CAAC;YAC5F,QAAQ,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,sCAAsC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;YAE9F,8BAA8B;YAC9B,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,gBAAgB,GAAG,CAAC,CAAC;YAEpD,IAAI,WAAW,GAAG,qBAAqB,EAAE,CAAC;gBACxC,qCAAqC;gBACrC,QAAQ,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,kDAAkD,CAAC,CAAC;gBACzF,IAAI,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,EAAE,CAAC,CAAC;YAChE,CAAC;iBAAM,CAAC;gBACN,qDAAqD;gBACrD,IAAI,CAAC,QAAQ,CAAC,EAAE,gBAAgB,EAAE,WAAW,EAAE,CAAC,CAAC;gBACjD,UAAU,CAAC,GAAG,EAAE;oBACd,IAAI,CAAC,WAAW,EAAE,CAAC;gBACrB,CAAC,EAAE,GAAG,CAAC,CAAC;YACV,CAAC;YACD,OAAO;QACT,CAAC;QAED,6BAA6B;QAC7B,QAAQ,CAAC,qBAAqB,EAAE,KAAK,CAAC,CAAC;IACzC,CAAC;IAED,MAAM;QACJ,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;YACxB,OAAO,CACL,oBAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,WAAW,EAAC,OAAO,EAAC,WAAW,EAAC,SAAS,EAAC,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC;gBAC3F,oBAAC,IAAI,IAAC,KAAK,EAAC,SAAS,EAAC,IAAI,sCAA4B;gBACtD,oBAAC,IAAI,IAAC,KAAK,EAAC,SAAS,IAAE,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAQ;gBACxD,oBAAC,GAAG,IAAC,SAAS,EAAE,CAAC;oBACf,oBAAC,IAAI,IAAC,KAAK,EAAC,SAAS,oEAEd,CACH,CACF,CACP,CAAC;QACJ,CAAC;QAED,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;IAC7B,CAAC;CACF"}
|
|
@@ -18,6 +18,7 @@ interface InputBoxProps {
|
|
|
18
18
|
subshellContext?: SubshellContext;
|
|
19
19
|
currentTokens?: number;
|
|
20
20
|
maxTokens?: number;
|
|
21
|
+
contextLimitReached?: boolean;
|
|
21
22
|
isShellRunning?: boolean;
|
|
22
23
|
backgroundTaskCount?: number;
|
|
23
24
|
initialValue?: string;
|
|
@@ -25,6 +26,9 @@ interface InputBoxProps {
|
|
|
25
26
|
onSetAutoModeSetup?: (callback: (enabled: boolean) => void) => void;
|
|
26
27
|
sessionQuotaExhausted?: boolean;
|
|
27
28
|
sessionQuotaTimeRemaining?: string;
|
|
29
|
+
subAgentCount?: number;
|
|
30
|
+
aiAutoSuggestEnabled?: boolean;
|
|
31
|
+
sessionCommands?: string[];
|
|
28
32
|
}
|
|
29
33
|
export declare const InputBox: React.FC<InputBoxProps>;
|
|
30
34
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InputBox.d.ts","sourceRoot":"","sources":["../../../src/ui/components/InputBox.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA+C,MAAM,OAAO,CAAC;AAIpE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAWzD,OAAO,EAAyC,cAAc,EAAE,MAAM,qCAAqC,CAAC;
|
|
1
|
+
{"version":3,"file":"InputBox.d.ts","sourceRoot":"","sources":["../../../src/ui/components/InputBox.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA+C,MAAM,OAAO,CAAC;AAIpE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAWzD,OAAO,EAAyC,cAAc,EAAE,MAAM,qCAAqC,CAAC;AAK5G,UAAU,aAAa;IACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,cAAc,EAAE,KAAK,IAAI,CAAC;IACtE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,kBAAkB,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,KAAK,IAAI,CAAC;IACpE,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AA+CD,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,aAAa,CAw9D3C,CAAC"}
|
|
@@ -13,6 +13,7 @@ import { FileTagAutocomplete } from './FileTagAutocomplete.js';
|
|
|
13
13
|
import { filterCommands } from '../../config/slash-commands.js';
|
|
14
14
|
import { getClipboardImages } from '../../services/clipboard-service.js';
|
|
15
15
|
import { useTerminalDimensions, TERMINAL_HEIGHT_CONSTANTS } from '../../hooks/useTerminalDimensions.js';
|
|
16
|
+
import { AIAutocompleteAgent, AI_AUTOCOMPLETE_DEBOUNCE_MS } from '../../services/ai-autocomplete-agent.js';
|
|
16
17
|
const getVisualLines = (text, width) => {
|
|
17
18
|
const logicalLines = text.split('\n');
|
|
18
19
|
const visualLines = [];
|
|
@@ -50,7 +51,7 @@ const getVisualLines = (text, width) => {
|
|
|
50
51
|
});
|
|
51
52
|
return visualLines;
|
|
52
53
|
};
|
|
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, sessionQuotaExhausted = false, sessionQuotaTimeRemaining = '' }) => {
|
|
54
|
+
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, contextLimitReached = false, isShellRunning = false, backgroundTaskCount = 0, initialValue = '', onValueChange, onSetAutoModeSetup, sessionQuotaExhausted = false, sessionQuotaTimeRemaining = '', subAgentCount = 0, aiAutoSuggestEnabled = false, sessionCommands = [] }) => {
|
|
54
55
|
// Use initialValue for first mount, but manage state internally after that
|
|
55
56
|
const [value, setValueInternal] = useState(initialValue);
|
|
56
57
|
const [cursorOffset, setCursorOffset] = useState(0);
|
|
@@ -68,11 +69,18 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
68
69
|
const [detectedIntent, setDetectedIntent] = useState('ai');
|
|
69
70
|
// Autocomplete State
|
|
70
71
|
const [autocompleteSuggestion, setAutocompleteSuggestion] = useState(null);
|
|
72
|
+
// AI Autocomplete State
|
|
73
|
+
const [aiAutocompleteSuggestion, setAiAutocompleteSuggestion] = useState(null);
|
|
74
|
+
const [isAiAutocompleteLoading, setIsAiAutocompleteLoading] = useState(false);
|
|
75
|
+
const aiAutocompleteDebounceRef = useRef(null);
|
|
76
|
+
const aiAutocompleteAbortRef = useRef(null);
|
|
71
77
|
// Undo/Redo State
|
|
72
78
|
const [undoStack, setUndoStack] = useState([]);
|
|
73
79
|
const [redoStack, setRedoStack] = useState([]);
|
|
74
80
|
// Selection State
|
|
75
81
|
const [selection, setSelection] = useState(null);
|
|
82
|
+
// Platform Detection
|
|
83
|
+
const isMac = process.platform === 'darwin';
|
|
76
84
|
// Reject Flash State (turns border red when submission is blocked)
|
|
77
85
|
const [rejectFlash, setRejectFlash] = useState(false);
|
|
78
86
|
// Session Quota Message State (shows quota exhausted message)
|
|
@@ -188,6 +196,26 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
188
196
|
}
|
|
189
197
|
return cwd;
|
|
190
198
|
}, [currentWorkingDirectory]);
|
|
199
|
+
// Determine current environment for command history isolation
|
|
200
|
+
// Format: 'local', 'ssh:user@host', or 'wsl:distroName'
|
|
201
|
+
const currentEnvironment = useMemo(() => {
|
|
202
|
+
if (!subshellContext)
|
|
203
|
+
return 'local';
|
|
204
|
+
if (subshellContext.type === 'ssh') {
|
|
205
|
+
const user = subshellContext.metadata?.username || 'user';
|
|
206
|
+
const host = subshellContext.metadata?.hostname || 'host';
|
|
207
|
+
return `ssh:${user}@${host}`;
|
|
208
|
+
}
|
|
209
|
+
if (subshellContext.type === 'wsl') {
|
|
210
|
+
const distro = subshellContext.metadata?.distroName || 'Ubuntu';
|
|
211
|
+
return `wsl:${distro}`;
|
|
212
|
+
}
|
|
213
|
+
if (subshellContext.type === 'docker') {
|
|
214
|
+
const container = subshellContext.metadata?.containerId || 'container';
|
|
215
|
+
return `docker:${container}`;
|
|
216
|
+
}
|
|
217
|
+
return 'local';
|
|
218
|
+
}, [subshellContext]);
|
|
191
219
|
// Autocomplete Logic
|
|
192
220
|
useEffect(() => {
|
|
193
221
|
if (!value || value.trim() === '') {
|
|
@@ -198,7 +226,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
198
226
|
// OR if we are in Auto mode and it looks like a command
|
|
199
227
|
const shouldSuggest = commandMode || (isAutoMode && detectedIntent === 'command');
|
|
200
228
|
if (shouldSuggest) {
|
|
201
|
-
const matches = CommandHistoryManager.getInstance().getMatches(value, currentDir);
|
|
229
|
+
const matches = CommandHistoryManager.getInstance().getMatches(value, currentDir, currentEnvironment);
|
|
202
230
|
if (matches.length > 0) {
|
|
203
231
|
setAutocompleteSuggestion(matches[0]);
|
|
204
232
|
}
|
|
@@ -209,7 +237,137 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
209
237
|
else {
|
|
210
238
|
setAutocompleteSuggestion(null);
|
|
211
239
|
}
|
|
212
|
-
}, [value, commandMode, isAutoMode, detectedIntent, currentDir]);
|
|
240
|
+
}, [value, commandMode, isAutoMode, detectedIntent, currentDir, currentEnvironment]);
|
|
241
|
+
// AI Autocomplete Logic (5-second debounce)
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
// Clear AI suggestion when value changes
|
|
244
|
+
setAiAutocompleteSuggestion(null);
|
|
245
|
+
// Clear any existing debounce timer
|
|
246
|
+
if (aiAutocompleteDebounceRef.current) {
|
|
247
|
+
clearTimeout(aiAutocompleteDebounceRef.current);
|
|
248
|
+
aiAutocompleteDebounceRef.current = null;
|
|
249
|
+
}
|
|
250
|
+
// Abort any pending AI request
|
|
251
|
+
if (aiAutocompleteAbortRef.current) {
|
|
252
|
+
aiAutocompleteAbortRef.current.abort();
|
|
253
|
+
aiAutocompleteAbortRef.current = null;
|
|
254
|
+
}
|
|
255
|
+
// Debug logging for trigger ref
|
|
256
|
+
/*
|
|
257
|
+
try {
|
|
258
|
+
if (detectedIntent !== 'ai' || value.length > 5) {
|
|
259
|
+
quickLog(`[InputBox] AI Effect: val="${value}", enabled=${aiAutoSuggestEnabled}, mode=${commandMode}, auto=${isAutoMode}, intent=${detectedIntent}`);
|
|
260
|
+
}
|
|
261
|
+
} catch (e) {}
|
|
262
|
+
*/
|
|
263
|
+
// Don't trigger AI autocomplete if disabled or not in command mode
|
|
264
|
+
if (!aiAutoSuggestEnabled) {
|
|
265
|
+
// quickLog('[InputBox] AI skipped: disabled');
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const shouldSuggest = commandMode || (isAutoMode && detectedIntent === 'command');
|
|
269
|
+
if (!shouldSuggest) {
|
|
270
|
+
// if (value.length > 3) quickLog(`[InputBox] AI skipped: !shouldSuggest (cmd=${commandMode}, auto=${isAutoMode}, intent=${detectedIntent})`);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
// Don't suggest for empty, very short, or slash commands
|
|
274
|
+
if (!value || value.trim().length < 2 || value.startsWith('/')) {
|
|
275
|
+
// quickLog('[InputBox] AI skipped: too short or slash');
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
// quickLog('[InputBox] AI Triggering debounce...');
|
|
279
|
+
// Set up 5-second debounce timer
|
|
280
|
+
aiAutocompleteDebounceRef.current = setTimeout(async () => {
|
|
281
|
+
setIsAiAutocompleteLoading(true);
|
|
282
|
+
// Create abort controller for this request
|
|
283
|
+
const abortController = new AbortController();
|
|
284
|
+
aiAutocompleteAbortRef.current = abortController;
|
|
285
|
+
try {
|
|
286
|
+
if (!aiAutoSuggestEnabled)
|
|
287
|
+
return; // double check inside timeout
|
|
288
|
+
// Get directory history using the correct method
|
|
289
|
+
const directoryHistory = CommandHistoryManager.getInstance().getDirectoryHistory(currentDir, currentEnvironment);
|
|
290
|
+
// Get files in current directory (local or remote)
|
|
291
|
+
let files = [];
|
|
292
|
+
try {
|
|
293
|
+
if (subshellContext && subshellContext.type !== 'local' && subshellContext.handler) {
|
|
294
|
+
// Remote/Subshell environment
|
|
295
|
+
// Check if we can list files
|
|
296
|
+
try {
|
|
297
|
+
const dirEntries = await subshellContext.handler.listDirectory(currentDir);
|
|
298
|
+
files = dirEntries
|
|
299
|
+
.slice(0, 50)
|
|
300
|
+
.map((e) => e.name + (e.type === 'directory' ? '/' : ''));
|
|
301
|
+
}
|
|
302
|
+
catch (remoteErr) {
|
|
303
|
+
// quickLog('Remote list error: ' + remoteErr);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
// Local environment
|
|
308
|
+
const dir = currentDir || process.cwd();
|
|
309
|
+
if (fs.existsSync(dir)) {
|
|
310
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
311
|
+
files = entries
|
|
312
|
+
.slice(0, 50)
|
|
313
|
+
.map(e => e.name + (e.isDirectory() ? '/' : ''));
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
catch (e) {
|
|
318
|
+
// Ignore file access errors
|
|
319
|
+
}
|
|
320
|
+
// Determine OS and Platform from subshell context if available
|
|
321
|
+
let osContext = process.platform;
|
|
322
|
+
let platformContext = process.platform;
|
|
323
|
+
if (subshellContext && subshellContext.type !== 'local' && subshellContext.metadata) {
|
|
324
|
+
const metaOs = subshellContext.metadata.os;
|
|
325
|
+
if (metaOs === 'windows') {
|
|
326
|
+
osContext = 'win32';
|
|
327
|
+
platformContext = 'win32';
|
|
328
|
+
}
|
|
329
|
+
else if (metaOs === 'macos') {
|
|
330
|
+
osContext = 'darwin';
|
|
331
|
+
platformContext = 'darwin';
|
|
332
|
+
}
|
|
333
|
+
else if (metaOs === 'linux') {
|
|
334
|
+
osContext = 'linux';
|
|
335
|
+
platformContext = 'linux';
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
const context = {
|
|
339
|
+
os: osContext,
|
|
340
|
+
platform: platformContext,
|
|
341
|
+
cwd: currentDir || process.cwd(),
|
|
342
|
+
directoryHistory: directoryHistory.slice(0, 10),
|
|
343
|
+
sessionCommands: sessionCommands.slice(-10),
|
|
344
|
+
files: files,
|
|
345
|
+
currentInput: value
|
|
346
|
+
};
|
|
347
|
+
const prediction = await AIAutocompleteAgent.predictCommand(context, abortController.signal);
|
|
348
|
+
if (prediction && !abortController.signal.aborted) {
|
|
349
|
+
setAiAutocompleteSuggestion(prediction);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
// Ignore errors (likely aborted)
|
|
354
|
+
}
|
|
355
|
+
finally {
|
|
356
|
+
setIsAiAutocompleteLoading(false);
|
|
357
|
+
if (aiAutocompleteAbortRef.current === abortController) {
|
|
358
|
+
aiAutocompleteAbortRef.current = null;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}, AI_AUTOCOMPLETE_DEBOUNCE_MS);
|
|
362
|
+
return () => {
|
|
363
|
+
if (aiAutocompleteDebounceRef.current) {
|
|
364
|
+
clearTimeout(aiAutocompleteDebounceRef.current);
|
|
365
|
+
}
|
|
366
|
+
if (aiAutocompleteAbortRef.current) {
|
|
367
|
+
aiAutocompleteAbortRef.current.abort();
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
}, [value, commandMode, isAutoMode, detectedIntent, aiAutoSuggestEnabled, currentDir, currentEnvironment, sessionCommands]);
|
|
213
371
|
// Auto-classification effect (Synchronous Heuristics Only)
|
|
214
372
|
useEffect(() => {
|
|
215
373
|
// Only run classification if in Auto Mode
|
|
@@ -398,6 +556,42 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
398
556
|
setCursorOffset(newValue.length);
|
|
399
557
|
setSlashAutocompleteVisible(false);
|
|
400
558
|
}
|
|
559
|
+
else if (value.startsWith('/models ') || value.startsWith('/model ')) {
|
|
560
|
+
// We're selecting a models subcommand
|
|
561
|
+
const prefix = value.startsWith('/models ') ? '/models ' : '/model ';
|
|
562
|
+
const newValue = `${prefix}${selected.name}`;
|
|
563
|
+
setValue(newValue);
|
|
564
|
+
setCursorOffset(newValue.length);
|
|
565
|
+
setSlashAutocompleteVisible(false);
|
|
566
|
+
}
|
|
567
|
+
else if (value.startsWith('/settings auto-suggest ')) {
|
|
568
|
+
// We're selecting an auto-suggest option (on/off)
|
|
569
|
+
const newValue = `/settings auto-suggest ${selected.name}`;
|
|
570
|
+
setValue(newValue);
|
|
571
|
+
setCursorOffset(newValue.length);
|
|
572
|
+
setSlashAutocompleteVisible(false);
|
|
573
|
+
}
|
|
574
|
+
else if (value.startsWith('/settings ')) {
|
|
575
|
+
// We're selecting a settings subcommand (e.g., auto-suggest)
|
|
576
|
+
const newValue = `/settings ${selected.name} `;
|
|
577
|
+
setValue(newValue);
|
|
578
|
+
setCursorOffset(newValue.length);
|
|
579
|
+
// Show the next level options (on/off for auto-suggest)
|
|
580
|
+
if (selected.name === 'auto-suggest') {
|
|
581
|
+
const optionMatches = filterCommands('settings auto-suggest ');
|
|
582
|
+
if (optionMatches.length > 0) {
|
|
583
|
+
setSlashAutocompleteCommands(optionMatches);
|
|
584
|
+
setSlashAutocompleteSelectedIndex(0);
|
|
585
|
+
setSlashAutocompleteScrollOffset(0);
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
setSlashAutocompleteVisible(false);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
setSlashAutocompleteVisible(false);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
401
595
|
else {
|
|
402
596
|
// Regular slash command, replace everything
|
|
403
597
|
const newValue = `/${selected.name} `;
|
|
@@ -465,6 +659,30 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
465
659
|
setSlashAutocompleteVisible(false);
|
|
466
660
|
}
|
|
467
661
|
}
|
|
662
|
+
else if (selected.name === 'models' || selected.name === 'model') {
|
|
663
|
+
const subcommandMatches = filterCommands('models ');
|
|
664
|
+
if (subcommandMatches.length > 0) {
|
|
665
|
+
setSlashAutocompleteCommands(subcommandMatches);
|
|
666
|
+
setSlashAutocompleteSelectedIndex(0);
|
|
667
|
+
setSlashAutocompleteScrollOffset(0);
|
|
668
|
+
// Keep autocomplete visible for subcommands
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
setSlashAutocompleteVisible(false);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
else if (selected.name === 'settings') {
|
|
675
|
+
const subcommandMatches = filterCommands('settings ');
|
|
676
|
+
if (subcommandMatches.length > 0) {
|
|
677
|
+
setSlashAutocompleteCommands(subcommandMatches);
|
|
678
|
+
setSlashAutocompleteSelectedIndex(0);
|
|
679
|
+
setSlashAutocompleteScrollOffset(0);
|
|
680
|
+
// Keep autocomplete visible for subcommands
|
|
681
|
+
}
|
|
682
|
+
else {
|
|
683
|
+
setSlashAutocompleteVisible(false);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
468
686
|
else {
|
|
469
687
|
setSlashAutocompleteVisible(false);
|
|
470
688
|
}
|
|
@@ -688,6 +906,20 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
688
906
|
setSlashAutocompleteVisible(false);
|
|
689
907
|
}
|
|
690
908
|
}
|
|
909
|
+
else if (newValue.startsWith('/models ') || newValue.startsWith('/model ')) {
|
|
910
|
+
// Models subcommands
|
|
911
|
+
const fullQuery = newValue.slice(1);
|
|
912
|
+
const matches = filterCommands(fullQuery);
|
|
913
|
+
if (matches.length > 0) {
|
|
914
|
+
setSlashAutocompleteCommands(matches);
|
|
915
|
+
setSlashAutocompleteVisible(true);
|
|
916
|
+
setSlashAutocompleteSelectedIndex(0);
|
|
917
|
+
setSlashAutocompleteScrollOffset(0);
|
|
918
|
+
}
|
|
919
|
+
else {
|
|
920
|
+
setSlashAutocompleteVisible(false);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
691
923
|
else {
|
|
692
924
|
setSlashAutocompleteVisible(false);
|
|
693
925
|
}
|
|
@@ -743,17 +975,52 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
743
975
|
}
|
|
744
976
|
return;
|
|
745
977
|
}
|
|
746
|
-
// Ctrl+Z: Undo
|
|
747
|
-
if (key.ctrl && input.toLowerCase() === 'z') {
|
|
978
|
+
// Ctrl+Z / Cmd+Z: Undo
|
|
979
|
+
if ((key.ctrl && input.toLowerCase() === 'z') || (key.meta && input.toLowerCase() === 'z')) {
|
|
748
980
|
handleUndo();
|
|
749
981
|
return;
|
|
750
982
|
}
|
|
751
|
-
// Ctrl+A: Select All
|
|
752
|
-
if (key.ctrl && input.toLowerCase() === 'a') {
|
|
983
|
+
// Ctrl+A / Cmd+A: Select All
|
|
984
|
+
if ((key.ctrl && input.toLowerCase() === 'a') || (key.meta && input.toLowerCase() === 'a')) {
|
|
753
985
|
setSelection({ start: 0, end: value.length });
|
|
754
986
|
setCursorOffset(value.length);
|
|
755
987
|
return;
|
|
756
988
|
}
|
|
989
|
+
// Home: Start of Line
|
|
990
|
+
// Note: In single-line inputs, this goes to start of text.
|
|
991
|
+
// In multi-line wrapped view, we ideally want start of logical line or start of text?
|
|
992
|
+
// Standard terminal Home = Start of command. Editor Home = Start of line.
|
|
993
|
+
// Let's stick to Start of Text for now as it's a single "input box".
|
|
994
|
+
// Home: Start of Line
|
|
995
|
+
// @ts-ignore
|
|
996
|
+
if (key.home) {
|
|
997
|
+
const width = (process.stdout.columns || 80) - 6;
|
|
998
|
+
const visualLines = getVisualLines(value, width);
|
|
999
|
+
const currentLine = visualLines.find(line => (cursorOffset >= line.start && cursorOffset < line.end) ||
|
|
1000
|
+
(cursorOffset === line.end && line.isHardEnd));
|
|
1001
|
+
if (currentLine) {
|
|
1002
|
+
setCursorOffset(currentLine.start);
|
|
1003
|
+
}
|
|
1004
|
+
else {
|
|
1005
|
+
setCursorOffset(0);
|
|
1006
|
+
}
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
// End: End of Line
|
|
1010
|
+
// @ts-ignore
|
|
1011
|
+
if (key.end) {
|
|
1012
|
+
const width = (process.stdout.columns || 80) - 6;
|
|
1013
|
+
const visualLines = getVisualLines(value, width);
|
|
1014
|
+
const currentLine = visualLines.find(line => (cursorOffset >= line.start && cursorOffset < line.end) ||
|
|
1015
|
+
(cursorOffset === line.end && line.isHardEnd));
|
|
1016
|
+
if (currentLine) {
|
|
1017
|
+
setCursorOffset(currentLine.end);
|
|
1018
|
+
}
|
|
1019
|
+
else {
|
|
1020
|
+
setCursorOffset(value.length);
|
|
1021
|
+
}
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
757
1024
|
// Note: Clipboard images are handled via Alt+V keyboard shortcut
|
|
758
1025
|
// DELETE CHAR - Only runs if Delete Word did NOT trigger
|
|
759
1026
|
// Triggers on:
|
|
@@ -870,6 +1137,20 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
870
1137
|
setSlashAutocompleteVisible(false);
|
|
871
1138
|
}
|
|
872
1139
|
}
|
|
1140
|
+
else if (newValue.startsWith('/models ') || newValue.startsWith('/model ')) {
|
|
1141
|
+
// Models subcommands
|
|
1142
|
+
const fullQuery = newValue.slice(1);
|
|
1143
|
+
const matches = filterCommands(fullQuery);
|
|
1144
|
+
if (matches.length > 0) {
|
|
1145
|
+
setSlashAutocompleteCommands(matches);
|
|
1146
|
+
setSlashAutocompleteVisible(true);
|
|
1147
|
+
setSlashAutocompleteSelectedIndex(0);
|
|
1148
|
+
setSlashAutocompleteScrollOffset(0);
|
|
1149
|
+
}
|
|
1150
|
+
else {
|
|
1151
|
+
setSlashAutocompleteVisible(false);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
873
1154
|
else {
|
|
874
1155
|
setSlashAutocompleteVisible(false);
|
|
875
1156
|
}
|
|
@@ -913,9 +1194,18 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
913
1194
|
setTimeout(() => setRejectFlash(false), 1000);
|
|
914
1195
|
return;
|
|
915
1196
|
}
|
|
916
|
-
// Check
|
|
1197
|
+
// Check context limit (only for AI mode, not command mode or slash commands)
|
|
917
1198
|
// Slash commands and terminal commands should always be allowed
|
|
918
1199
|
const isSlashCommand = value.trim().startsWith('/');
|
|
1200
|
+
if (!commandMode && !isCommandIntent && !isSlashCommand && contextLimitReached) {
|
|
1201
|
+
setRejectFlash(true);
|
|
1202
|
+
setTimeout(() => {
|
|
1203
|
+
setRejectFlash(false);
|
|
1204
|
+
}, 3000); // Flash red for 3 seconds
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
// Check session quota (only for AI mode, not command mode or slash commands)
|
|
1208
|
+
// Slash commands and terminal commands should always be allowed
|
|
919
1209
|
if (!commandMode && !isCommandIntent && !isSlashCommand && sessionQuotaExhausted) {
|
|
920
1210
|
setRejectFlash(true);
|
|
921
1211
|
setShowQuotaMessage(true);
|
|
@@ -1034,8 +1324,8 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
1034
1324
|
return;
|
|
1035
1325
|
}
|
|
1036
1326
|
if (key.leftArrow) {
|
|
1037
|
-
if (key.ctrl) {
|
|
1038
|
-
// Ctrl+Left: Move word backwards
|
|
1327
|
+
if (key.ctrl || key.meta) {
|
|
1328
|
+
// Ctrl+Left / Meta+Left (Option+Left): Move word backwards
|
|
1039
1329
|
let newOffset = cursorOffset;
|
|
1040
1330
|
if (newOffset > 0) {
|
|
1041
1331
|
// Skip whitespace backwards
|
|
@@ -1056,17 +1346,20 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
1056
1346
|
}
|
|
1057
1347
|
if (key.rightArrow) {
|
|
1058
1348
|
// Autocomplete Logic (Only at end of line)
|
|
1059
|
-
|
|
1349
|
+
// AI suggestion takes priority over passive suggestion
|
|
1350
|
+
const effectiveSuggestion = aiAutocompleteSuggestion || autocompleteSuggestion;
|
|
1351
|
+
if (effectiveSuggestion && cursorOffset === value.length) {
|
|
1060
1352
|
if (key.ctrl) {
|
|
1061
1353
|
// Ctrl+Right: Accept FULL suggestion
|
|
1062
|
-
setValue(
|
|
1063
|
-
setCursorOffset(
|
|
1354
|
+
setValue(effectiveSuggestion);
|
|
1355
|
+
setCursorOffset(effectiveSuggestion.length);
|
|
1064
1356
|
setAutocompleteSuggestion(null);
|
|
1357
|
+
setAiAutocompleteSuggestion(null);
|
|
1065
1358
|
return;
|
|
1066
1359
|
}
|
|
1067
1360
|
else {
|
|
1068
1361
|
// Right: Accept NEXT WORD
|
|
1069
|
-
const remaining =
|
|
1362
|
+
const remaining = effectiveSuggestion.slice(value.length);
|
|
1070
1363
|
// Match next chunk of non-whitespace (including preceding whitespace)
|
|
1071
1364
|
const match = remaining.match(/^(\s*\S+)/);
|
|
1072
1365
|
if (match) {
|
|
@@ -1086,8 +1379,8 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
1086
1379
|
}
|
|
1087
1380
|
}
|
|
1088
1381
|
// Navigation Logic (if not completing)
|
|
1089
|
-
if (key.ctrl) {
|
|
1090
|
-
// Ctrl+Right: Move word forwards
|
|
1382
|
+
if (key.ctrl || key.meta) {
|
|
1383
|
+
// Ctrl+Right / Meta+Right (Option+Right): Move word forwards
|
|
1091
1384
|
let newOffset = cursorOffset;
|
|
1092
1385
|
if (newOffset < value.length) {
|
|
1093
1386
|
// Skip non-whitespace forwards
|
|
@@ -1230,6 +1523,20 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
1230
1523
|
setSlashAutocompleteVisible(false);
|
|
1231
1524
|
}
|
|
1232
1525
|
}
|
|
1526
|
+
else if (newValue.startsWith('/models ') || newValue.startsWith('/model ')) {
|
|
1527
|
+
// Models subcommands (when user types "/models " or "/model ")
|
|
1528
|
+
const fullQuery = newValue.slice(1); // Remove leading "/", pass "models <subquery>" to filterCommands
|
|
1529
|
+
const matches = filterCommands(fullQuery);
|
|
1530
|
+
if (matches.length > 0) {
|
|
1531
|
+
setSlashAutocompleteCommands(matches);
|
|
1532
|
+
setSlashAutocompleteVisible(true);
|
|
1533
|
+
setSlashAutocompleteSelectedIndex(0);
|
|
1534
|
+
setSlashAutocompleteScrollOffset(0);
|
|
1535
|
+
}
|
|
1536
|
+
else {
|
|
1537
|
+
setSlashAutocompleteVisible(false);
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1233
1540
|
else {
|
|
1234
1541
|
setSlashAutocompleteVisible(false);
|
|
1235
1542
|
}
|
|
@@ -1315,7 +1622,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
1315
1622
|
if (trimmedValue) {
|
|
1316
1623
|
// Save to history if it was a command
|
|
1317
1624
|
if (commandMode) {
|
|
1318
|
-
CommandHistoryManager.getInstance().addCommand(trimmedValue, currentDir);
|
|
1625
|
+
CommandHistoryManager.getInstance().addCommand(trimmedValue, currentDir, currentEnvironment);
|
|
1319
1626
|
}
|
|
1320
1627
|
// Resolve file tags (@filename -> absolute path)
|
|
1321
1628
|
let resolvedValue = trimmedValue;
|
|
@@ -1482,10 +1789,14 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
1482
1789
|
}
|
|
1483
1790
|
// Render Autocomplete Ghost Text
|
|
1484
1791
|
// Only on the last line, if suggestion exists and matches start
|
|
1485
|
-
|
|
1486
|
-
|
|
1792
|
+
// AI suggestion takes priority over passive suggestion
|
|
1793
|
+
const effectiveSuggestion = aiAutocompleteSuggestion || autocompleteSuggestion;
|
|
1794
|
+
if (isLastLine && effectiveSuggestion && effectiveSuggestion.startsWith(value)) {
|
|
1795
|
+
const suffix = effectiveSuggestion.slice(value.length);
|
|
1487
1796
|
if (suffix) {
|
|
1488
|
-
|
|
1797
|
+
// Use slightly different color for AI suggestion to differentiate
|
|
1798
|
+
const ghostColor = aiAutocompleteSuggestion ? '#888888' : 'gray';
|
|
1799
|
+
renderedChars.push(React.createElement(Text, { key: "ghost", color: ghostColor }, suffix));
|
|
1489
1800
|
}
|
|
1490
1801
|
}
|
|
1491
1802
|
if (renderedChars.length === 0) {
|
|
@@ -1495,7 +1806,12 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
1495
1806
|
}),
|
|
1496
1807
|
endLine < totalVisualLines && React.createElement(Text, { color: "gray" }, "\u2193 ...")));
|
|
1497
1808
|
};
|
|
1498
|
-
return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: rejectFlash ? "#ff3366" :
|
|
1809
|
+
return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: rejectFlash ? "#ff3366" :
|
|
1810
|
+
contextLimitReached ? "#ff3366" : // Red when context limit reached
|
|
1811
|
+
sessionQuotaExhausted ? "#ff3366" : // Red when quota exhausted
|
|
1812
|
+
backgroundMode ? "#9966ff" :
|
|
1813
|
+
commandMode ? "#00cc66" :
|
|
1814
|
+
"#257aa5ff", paddingX: 1, paddingY: 0, width: "100%" },
|
|
1499
1815
|
React.createElement(Box, { marginY: 1, justifyContent: "space-between", width: "100%" },
|
|
1500
1816
|
React.createElement(Box, null,
|
|
1501
1817
|
subshellContext && subshellContext.type !== 'local' && (React.createElement(Breadcrumbs, { context: subshellContext })),
|
|
@@ -1519,12 +1835,18 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
1519
1835
|
React.createElement(Text, { color: "#ff3366", bold: true },
|
|
1520
1836
|
"Session quota reached. Try again in ",
|
|
1521
1837
|
sessionQuotaTimeRemaining))),
|
|
1838
|
+
contextLimitReached && (React.createElement(Box, { justifyContent: "flex-end", marginBottom: 1 },
|
|
1839
|
+
React.createElement(Text, { color: "#ff3366", bold: true }, "\u26A0\uFE0F Context limit reached. Use /new to start a fresh chat."))),
|
|
1522
1840
|
React.createElement(Box, { flexDirection: "row", width: "100%" },
|
|
1523
1841
|
React.createElement(Text, { color: "#666666" }, "> "),
|
|
1524
1842
|
renderInput()),
|
|
1525
1843
|
React.createElement(Box, { marginY: 1, justifyContent: "space-between", width: "100%" },
|
|
1526
1844
|
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')),
|
|
1527
1845
|
React.createElement(Box, { gap: 1 },
|
|
1846
|
+
subAgentCount > 0 && (React.createElement(Text, { color: "#00ccff", bold: true },
|
|
1847
|
+
"[sub-agent: ",
|
|
1848
|
+
subAgentCount,
|
|
1849
|
+
"]")),
|
|
1528
1850
|
backgroundTaskCount > 0 && (React.createElement(Text, { color: "#9966ff", bold: true },
|
|
1529
1851
|
"[bkg tasks: ",
|
|
1530
1852
|
backgroundTaskCount,
|