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.
Files changed (156) hide show
  1. package/dist/cli-adapter.d.ts +76 -3
  2. package/dist/cli-adapter.d.ts.map +1 -1
  3. package/dist/cli-adapter.js +593 -230
  4. package/dist/cli-adapter.js.map +1 -1
  5. package/dist/config/mcp-config-manager.d.ts +21 -0
  6. package/dist/config/mcp-config-manager.d.ts.map +1 -1
  7. package/dist/config/mcp-config-manager.js +184 -1
  8. package/dist/config/mcp-config-manager.js.map +1 -1
  9. package/dist/config/models.d.ts +1 -0
  10. package/dist/config/models.d.ts.map +1 -1
  11. package/dist/config/models.js +9 -2
  12. package/dist/config/models.js.map +1 -1
  13. package/dist/config/slash-commands.d.ts +3 -0
  14. package/dist/config/slash-commands.d.ts.map +1 -1
  15. package/dist/config/slash-commands.js +39 -4
  16. package/dist/config/slash-commands.js.map +1 -1
  17. package/dist/config/types.d.ts +2 -0
  18. package/dist/config/types.d.ts.map +1 -1
  19. package/dist/config/types.js +1 -0
  20. package/dist/config/types.js.map +1 -1
  21. package/dist/index.js +60 -11
  22. package/dist/index.js.map +1 -1
  23. package/dist/mcp/mcp-command-handler.d.ts +34 -3
  24. package/dist/mcp/mcp-command-handler.d.ts.map +1 -1
  25. package/dist/mcp/mcp-command-handler.js +171 -83
  26. package/dist/mcp/mcp-command-handler.js.map +1 -1
  27. package/dist/mcp/mcp-server-manager.d.ts.map +1 -1
  28. package/dist/mcp/mcp-server-manager.js +9 -23
  29. package/dist/mcp/mcp-server-manager.js.map +1 -1
  30. package/dist/mcp/mcp-tool-wrapper.d.ts.map +1 -1
  31. package/dist/mcp/mcp-tool-wrapper.js +42 -5
  32. package/dist/mcp/mcp-tool-wrapper.js.map +1 -1
  33. package/dist/services/ai-autocomplete-agent.d.ts +39 -0
  34. package/dist/services/ai-autocomplete-agent.d.ts.map +1 -0
  35. package/dist/services/ai-autocomplete-agent.js +189 -0
  36. package/dist/services/ai-autocomplete-agent.js.map +1 -0
  37. package/dist/services/ai-service-client.d.ts +25 -0
  38. package/dist/services/ai-service-client.d.ts.map +1 -1
  39. package/dist/services/ai-service-client.js +162 -1
  40. package/dist/services/ai-service-client.js.map +1 -1
  41. package/dist/services/api-client.d.ts +9 -0
  42. package/dist/services/api-client.d.ts.map +1 -1
  43. package/dist/services/api-client.js +25 -0
  44. package/dist/services/api-client.js.map +1 -1
  45. package/dist/services/auth-handler.js +1 -1
  46. package/dist/services/auth-handler.js.map +1 -1
  47. package/dist/services/input-detection-agent.d.ts +40 -0
  48. package/dist/services/input-detection-agent.d.ts.map +1 -0
  49. package/dist/services/input-detection-agent.js +213 -0
  50. package/dist/services/input-detection-agent.js.map +1 -0
  51. package/dist/services/input-requirement-detector.d.ts +28 -0
  52. package/dist/services/input-requirement-detector.d.ts.map +1 -0
  53. package/dist/services/input-requirement-detector.js +203 -0
  54. package/dist/services/input-requirement-detector.js.map +1 -0
  55. package/dist/services/local-chat-storage.d.ts +21 -0
  56. package/dist/services/local-chat-storage.d.ts.map +1 -1
  57. package/dist/services/local-chat-storage.js +138 -43
  58. package/dist/services/local-chat-storage.js.map +1 -1
  59. package/dist/services/monitored-shell-manager.d.ts +120 -0
  60. package/dist/services/monitored-shell-manager.d.ts.map +1 -0
  61. package/dist/services/monitored-shell-manager.js +239 -0
  62. package/dist/services/monitored-shell-manager.js.map +1 -0
  63. package/dist/services/ollama-service.d.ts +197 -0
  64. package/dist/services/ollama-service.d.ts.map +1 -0
  65. package/dist/services/ollama-service.js +324 -0
  66. package/dist/services/ollama-service.js.map +1 -0
  67. package/dist/services/shell-input-agent.d.ts +89 -0
  68. package/dist/services/shell-input-agent.d.ts.map +1 -0
  69. package/dist/services/shell-input-agent.js +361 -0
  70. package/dist/services/shell-input-agent.js.map +1 -0
  71. package/dist/services/sub-agent-manager.d.ts +139 -0
  72. package/dist/services/sub-agent-manager.d.ts.map +1 -0
  73. package/dist/services/sub-agent-manager.js +517 -0
  74. package/dist/services/sub-agent-manager.js.map +1 -0
  75. package/dist/tools/background-command.d.ts.map +1 -1
  76. package/dist/tools/background-command.js +33 -13
  77. package/dist/tools/background-command.js.map +1 -1
  78. package/dist/tools/command.d.ts.map +1 -1
  79. package/dist/tools/command.js +64 -1
  80. package/dist/tools/command.js.map +1 -1
  81. package/dist/tools/file-ops.d.ts.map +1 -1
  82. package/dist/tools/file-ops.js +33 -19
  83. package/dist/tools/file-ops.js.map +1 -1
  84. package/dist/tools/get-diff.js +1 -1
  85. package/dist/tools/get-diff.js.map +1 -1
  86. package/dist/tools/grep-search.d.ts.map +1 -1
  87. package/dist/tools/grep-search.js +41 -15
  88. package/dist/tools/grep-search.js.map +1 -1
  89. package/dist/tools/plan-mode.js +3 -3
  90. package/dist/tools/plan-mode.js.map +1 -1
  91. package/dist/tools/registry.js +1 -1
  92. package/dist/tools/registry.js.map +1 -1
  93. package/dist/tools/sub-agent.d.ts +9 -0
  94. package/dist/tools/sub-agent.d.ts.map +1 -0
  95. package/dist/tools/sub-agent.js +232 -0
  96. package/dist/tools/sub-agent.js.map +1 -0
  97. package/dist/tools/task-complete.d.ts.map +1 -1
  98. package/dist/tools/task-complete.js +14 -26
  99. package/dist/tools/task-complete.js.map +1 -1
  100. package/dist/ui/components/App.d.ts +45 -2
  101. package/dist/ui/components/App.d.ts.map +1 -1
  102. package/dist/ui/components/App.js +605 -96
  103. package/dist/ui/components/App.js.map +1 -1
  104. package/dist/ui/components/CircularSelectInput.d.ts +24 -0
  105. package/dist/ui/components/CircularSelectInput.d.ts.map +1 -0
  106. package/dist/ui/components/CircularSelectInput.js +71 -0
  107. package/dist/ui/components/CircularSelectInput.js.map +1 -0
  108. package/dist/ui/components/ErrorBoundary.d.ts +3 -2
  109. package/dist/ui/components/ErrorBoundary.d.ts.map +1 -1
  110. package/dist/ui/components/ErrorBoundary.js +29 -1
  111. package/dist/ui/components/ErrorBoundary.js.map +1 -1
  112. package/dist/ui/components/InputBox.d.ts +4 -0
  113. package/dist/ui/components/InputBox.d.ts.map +1 -1
  114. package/dist/ui/components/InputBox.js +343 -21
  115. package/dist/ui/components/InputBox.js.map +1 -1
  116. package/dist/ui/components/InteractiveShell.d.ts +6 -0
  117. package/dist/ui/components/InteractiveShell.d.ts.map +1 -1
  118. package/dist/ui/components/InteractiveShell.js +57 -6
  119. package/dist/ui/components/InteractiveShell.js.map +1 -1
  120. package/dist/ui/components/MCPAddScreen.d.ts +13 -0
  121. package/dist/ui/components/MCPAddScreen.d.ts.map +1 -0
  122. package/dist/ui/components/MCPAddScreen.js +54 -0
  123. package/dist/ui/components/MCPAddScreen.js.map +1 -0
  124. package/dist/ui/components/MCPListScreen.d.ts +17 -0
  125. package/dist/ui/components/MCPListScreen.d.ts.map +1 -0
  126. package/dist/ui/components/MCPListScreen.js +50 -0
  127. package/dist/ui/components/MCPListScreen.js.map +1 -0
  128. package/dist/ui/components/MCPServerListScreen.d.ts +16 -0
  129. package/dist/ui/components/MCPServerListScreen.d.ts.map +1 -0
  130. package/dist/ui/components/MCPServerListScreen.js +59 -0
  131. package/dist/ui/components/MCPServerListScreen.js.map +1 -0
  132. package/dist/ui/components/MonitorModeAIPanel.d.ts +23 -0
  133. package/dist/ui/components/MonitorModeAIPanel.d.ts.map +1 -0
  134. package/dist/ui/components/MonitorModeAIPanel.js +69 -0
  135. package/dist/ui/components/MonitorModeAIPanel.js.map +1 -0
  136. package/dist/ui/components/MultiLineInput.d.ts +13 -0
  137. package/dist/ui/components/MultiLineInput.d.ts.map +1 -0
  138. package/dist/ui/components/MultiLineInput.js +289 -0
  139. package/dist/ui/components/MultiLineInput.js.map +1 -0
  140. package/dist/ui/components/StatusBar.d.ts +2 -0
  141. package/dist/ui/components/StatusBar.d.ts.map +1 -1
  142. package/dist/ui/components/StatusBar.js +33 -2
  143. package/dist/ui/components/StatusBar.js.map +1 -1
  144. package/dist/ui/components/ToolExecutionMessage.d.ts.map +1 -1
  145. package/dist/ui/components/ToolExecutionMessage.js +231 -13
  146. package/dist/ui/components/ToolExecutionMessage.js.map +1 -1
  147. package/dist/ui/components/VersionUpdatePrompt.d.ts.map +1 -1
  148. package/dist/ui/components/VersionUpdatePrompt.js +3 -2
  149. package/dist/ui/components/VersionUpdatePrompt.js.map +1 -1
  150. package/dist/utils/command-history.d.ts +12 -2
  151. package/dist/utils/command-history.d.ts.map +1 -1
  152. package/dist/utils/command-history.js +57 -13
  153. package/dist/utils/command-history.js.map +1 -1
  154. package/dist/utils/input-classifier.js +1 -1
  155. package/dist/utils/input-classifier.js.map +1 -1
  156. 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;AAWjD,MAAM,OAAO,aAAc,SAAQ,SAAuB;IACxD,YAAY,KAAY;QACtB,KAAK,CAAC,KAAK,CAAC,CAAC;QACb,IAAI,CAAC,KAAK,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IACnC,CAAC;IAED,MAAM,CAAC,wBAAwB,CAAC,KAAY;QAC1C,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACnC,CAAC;IAED,iBAAiB,CAAC,KAAY,EAAE,SAAc;QAC5C,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"}
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;AAG5G,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,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;CACpC;AA+CD,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,aAAa,CAmoD3C,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 session quota (only for AI mode, not command mode or slash commands)
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
- if (autocompleteSuggestion && cursorOffset === value.length) {
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(autocompleteSuggestion);
1063
- setCursorOffset(autocompleteSuggestion.length);
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 = autocompleteSuggestion.slice(value.length);
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
- if (isLastLine && autocompleteSuggestion && autocompleteSuggestion.startsWith(value)) {
1486
- const suffix = autocompleteSuggestion.slice(value.length);
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
- renderedChars.push(React.createElement(Text, { key: "ghost", color: "gray" }, suffix));
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" : (backgroundMode ? "#9966ff" : (commandMode ? "#00cc66" : "#257aa5ff")), paddingX: 1, paddingY: 0, width: "100%" },
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,