centaurus-cli 2.5.2 → 2.5.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 (246) hide show
  1. package/AUTH_FLOW.md +138 -0
  2. package/CONFIG_GUIDE.md +249 -0
  3. package/dist/config/models.d.ts +1 -1
  4. package/dist/config/models.d.ts.map +1 -1
  5. package/dist/config/models.js +2 -0
  6. package/dist/config/models.js.map +1 -1
  7. package/dist/ui/components/App.d.ts.map +1 -1
  8. package/dist/ui/components/App.js +104 -62
  9. package/dist/ui/components/App.js.map +1 -1
  10. package/dist/ui/components/FontRecommendation.d.ts +1 -0
  11. package/dist/ui/components/FontRecommendation.d.ts.map +1 -0
  12. package/dist/ui/components/FontRecommendation.js +1 -0
  13. package/dist/ui/components/FontRecommendation.js.map +1 -0
  14. package/dist/ui/components/InputBox.d.ts.map +1 -1
  15. package/dist/ui/components/InputBox.js +3 -2
  16. package/dist/ui/components/InputBox.js.map +1 -1
  17. package/dist/ui/components/MarkdownRenderer.d.ts.map +1 -1
  18. package/dist/ui/components/MarkdownRenderer.js +34 -9
  19. package/dist/ui/components/MarkdownRenderer.js.map +1 -1
  20. package/dist/ui/components/MessageDisplay.js +2 -2
  21. package/dist/ui/components/StreamingMessageDisplay.d.ts.map +1 -1
  22. package/dist/ui/components/StreamingMessageDisplay.js +3 -3
  23. package/dist/ui/components/StreamingMessageDisplay.js.map +1 -1
  24. package/dist/ui/components/ThinkingDisplay.d.ts +13 -0
  25. package/dist/ui/components/ThinkingDisplay.d.ts.map +1 -0
  26. package/dist/ui/components/ThinkingDisplay.js +41 -0
  27. package/dist/ui/components/ThinkingDisplay.js.map +1 -0
  28. package/dist/utils/version-checker.d.ts.map +1 -1
  29. package/dist/utils/version-checker.js +3 -31
  30. package/dist/utils/version-checker.js.map +1 -1
  31. package/package.json +5 -5
  32. package/dist/ai/provider-factory.d.ts +0 -6
  33. package/dist/ai/provider-factory.d.ts.map +0 -1
  34. package/dist/ai/provider-factory.js +0 -27
  35. package/dist/ai/provider-factory.js.map +0 -1
  36. package/dist/ai/providers/base.d.ts +0 -25
  37. package/dist/ai/providers/base.d.ts.map +0 -1
  38. package/dist/ai/providers/base.js +0 -9
  39. package/dist/ai/providers/base.js.map +0 -1
  40. package/dist/ai/providers/gemini.d.ts +0 -34
  41. package/dist/ai/providers/gemini.d.ts.map +0 -1
  42. package/dist/ai/providers/gemini.js +0 -146
  43. package/dist/ai/providers/gemini.js.map +0 -1
  44. package/dist/commands/view-duplication-logs.d.ts +0 -5
  45. package/dist/commands/view-duplication-logs.d.ts.map +0 -1
  46. package/dist/commands/view-duplication-logs.js +0 -14
  47. package/dist/commands/view-duplication-logs.js.map +0 -1
  48. package/dist/context/__tests__/command-detector.test.d.ts +0 -14
  49. package/dist/context/__tests__/command-detector.test.d.ts.map +0 -1
  50. package/dist/context/__tests__/command-detector.test.js +0 -318
  51. package/dist/context/__tests__/command-detector.test.js.map +0 -1
  52. package/dist/context/__tests__/context-manager.test.d.ts +0 -16
  53. package/dist/context/__tests__/context-manager.test.d.ts.map +0 -1
  54. package/dist/context/__tests__/context-manager.test.js +0 -375
  55. package/dist/context/__tests__/context-manager.test.js.map +0 -1
  56. package/dist/context/__tests__/error-handling.test.d.ts +0 -15
  57. package/dist/context/__tests__/error-handling.test.d.ts.map +0 -1
  58. package/dist/context/__tests__/error-handling.test.js +0 -447
  59. package/dist/context/__tests__/error-handling.test.js.map +0 -1
  60. package/dist/context/handlers/__tests__/docker-handler.test.d.ts +0 -13
  61. package/dist/context/handlers/__tests__/docker-handler.test.d.ts.map +0 -1
  62. package/dist/context/handlers/__tests__/docker-handler.test.js +0 -285
  63. package/dist/context/handlers/__tests__/docker-handler.test.js.map +0 -1
  64. package/dist/context/handlers/__tests__/ssh-handler.test.d.ts +0 -13
  65. package/dist/context/handlers/__tests__/ssh-handler.test.d.ts.map +0 -1
  66. package/dist/context/handlers/__tests__/ssh-handler.test.js +0 -251
  67. package/dist/context/handlers/__tests__/ssh-handler.test.js.map +0 -1
  68. package/dist/context/handlers/__tests__/wsl-handler.test.d.ts +0 -7
  69. package/dist/context/handlers/__tests__/wsl-handler.test.d.ts.map +0 -1
  70. package/dist/context/handlers/__tests__/wsl-handler.test.js +0 -331
  71. package/dist/context/handlers/__tests__/wsl-handler.test.js.map +0 -1
  72. package/dist/index-custom.d.ts +0 -3
  73. package/dist/index-custom.d.ts.map +0 -1
  74. package/dist/index-custom.js +0 -65
  75. package/dist/index-custom.js.map +0 -1
  76. package/dist/prompts/system-prompt.d.ts +0 -47
  77. package/dist/prompts/system-prompt.d.ts.map +0 -1
  78. package/dist/prompts/system-prompt.js +0 -377
  79. package/dist/prompts/system-prompt.js.map +0 -1
  80. package/dist/providers/GoogleProvider.d.ts +0 -26
  81. package/dist/providers/GoogleProvider.d.ts.map +0 -1
  82. package/dist/providers/GoogleProvider.js +0 -313
  83. package/dist/providers/GoogleProvider.js.map +0 -1
  84. package/dist/providers/Provider.d.ts +0 -114
  85. package/dist/providers/Provider.d.ts.map +0 -1
  86. package/dist/providers/Provider.js +0 -44
  87. package/dist/providers/Provider.js.map +0 -1
  88. package/dist/services/__tests__/ai-context-injector.test.d.ts +0 -15
  89. package/dist/services/__tests__/ai-context-injector.test.d.ts.map +0 -1
  90. package/dist/services/__tests__/ai-context-injector.test.js +0 -326
  91. package/dist/services/__tests__/ai-context-injector.test.js.map +0 -1
  92. package/dist/src/context/types.js +0 -27
  93. package/dist/src/services/ai-context-injector.js +0 -96
  94. package/dist/src/services/ai-service-client.js +0 -270
  95. package/dist/src/services/api-client.js +0 -349
  96. package/dist/src/tools/types.js +0 -1
  97. package/dist/src/types/index.js +0 -1
  98. package/dist/test/context/types.js +0 -27
  99. package/dist/test/services/__tests__/ai-context-injector.test.js +0 -325
  100. package/dist/test/services/ai-context-injector.js +0 -96
  101. package/dist/test/services/ai-service-client.js +0 -270
  102. package/dist/test/services/api-client.js +0 -349
  103. package/dist/test/tools/types.js +0 -1
  104. package/dist/test/types/index.js +0 -1
  105. package/dist/test-ai-context-injector.js +0 -97
  106. package/dist/tests/automated-verification.d.ts +0 -27
  107. package/dist/tests/automated-verification.d.ts.map +0 -1
  108. package/dist/tests/automated-verification.js +0 -359
  109. package/dist/tests/automated-verification.js.map +0 -1
  110. package/dist/tests/integration-tests.d.ts +0 -50
  111. package/dist/tests/integration-tests.d.ts.map +0 -1
  112. package/dist/tests/integration-tests.js +0 -648
  113. package/dist/tests/integration-tests.js.map +0 -1
  114. package/dist/tools/file-ops-test.d.ts +0 -6
  115. package/dist/tools/file-ops-test.d.ts.map +0 -1
  116. package/dist/tools/file-ops-test.js +0 -197
  117. package/dist/tools/file-ops-test.js.map +0 -1
  118. package/dist/ui/DisplayHistory.d.ts +0 -53
  119. package/dist/ui/DisplayHistory.d.ts.map +0 -1
  120. package/dist/ui/DisplayHistory.js +0 -82
  121. package/dist/ui/DisplayHistory.js.map +0 -1
  122. package/dist/ui/clack-ui.d.ts +0 -83
  123. package/dist/ui/clack-ui.d.ts.map +0 -1
  124. package/dist/ui/clack-ui.js +0 -304
  125. package/dist/ui/clack-ui.js.map +0 -1
  126. package/dist/ui/components/DisplayItemRenderer.d.ts +0 -18
  127. package/dist/ui/components/DisplayItemRenderer.d.ts.map +0 -1
  128. package/dist/ui/components/DisplayItemRenderer.js +0 -53
  129. package/dist/ui/components/DisplayItemRenderer.js.map +0 -1
  130. package/dist/ui/components/DynamicMessage.d.ts +0 -13
  131. package/dist/ui/components/DynamicMessage.d.ts.map +0 -1
  132. package/dist/ui/components/DynamicMessage.js +0 -27
  133. package/dist/ui/components/DynamicMessage.js.map +0 -1
  134. package/dist/ui/components/FileViewerScreen.d.ts +0 -14
  135. package/dist/ui/components/FileViewerScreen.d.ts.map +0 -1
  136. package/dist/ui/components/FileViewerScreen.js +0 -74
  137. package/dist/ui/components/FileViewerScreen.js.map +0 -1
  138. package/dist/ui/components/ScrollableContent.d.ts +0 -7
  139. package/dist/ui/components/ScrollableContent.d.ts.map +0 -1
  140. package/dist/ui/components/ScrollableContent.js +0 -6
  141. package/dist/ui/components/ScrollableContent.js.map +0 -1
  142. package/dist/ui/components/ScrollableMessageList.d.ts +0 -10
  143. package/dist/ui/components/ScrollableMessageList.d.ts.map +0 -1
  144. package/dist/ui/components/ScrollableMessageList.js +0 -133
  145. package/dist/ui/components/ScrollableMessageList.js.map +0 -1
  146. package/dist/ui/components/ScrollableScreen.d.ts +0 -9
  147. package/dist/ui/components/ScrollableScreen.d.ts.map +0 -1
  148. package/dist/ui/components/ScrollableScreen.js +0 -22
  149. package/dist/ui/components/ScrollableScreen.js.map +0 -1
  150. package/dist/ui/components/StaticMessageHistory.d.ts +0 -14
  151. package/dist/ui/components/StaticMessageHistory.d.ts.map +0 -1
  152. package/dist/ui/components/StaticMessageHistory.js +0 -19
  153. package/dist/ui/components/StaticMessageHistory.js.map +0 -1
  154. package/dist/ui/components/code-block.d.ts +0 -10
  155. package/dist/ui/components/code-block.d.ts.map +0 -1
  156. package/dist/ui/components/code-block.js +0 -74
  157. package/dist/ui/components/code-block.js.map +0 -1
  158. package/dist/ui/components/confirm-prompt.d.ts +0 -12
  159. package/dist/ui/components/confirm-prompt.d.ts.map +0 -1
  160. package/dist/ui/components/confirm-prompt.js +0 -104
  161. package/dist/ui/components/confirm-prompt.js.map +0 -1
  162. package/dist/ui/components/diff-viewer.d.ts +0 -9
  163. package/dist/ui/components/diff-viewer.d.ts.map +0 -1
  164. package/dist/ui/components/diff-viewer.js +0 -57
  165. package/dist/ui/components/diff-viewer.js.map +0 -1
  166. package/dist/ui/components/input-box.d.ts +0 -18
  167. package/dist/ui/components/input-box.d.ts.map +0 -1
  168. package/dist/ui/components/input-box.js +0 -157
  169. package/dist/ui/components/input-box.js.map +0 -1
  170. package/dist/ui/components/keyboard-help.d.ts +0 -7
  171. package/dist/ui/components/keyboard-help.d.ts.map +0 -1
  172. package/dist/ui/components/keyboard-help.js +0 -43
  173. package/dist/ui/components/keyboard-help.js.map +0 -1
  174. package/dist/ui/components/loading-indicator.d.ts +0 -3
  175. package/dist/ui/components/loading-indicator.d.ts.map +0 -1
  176. package/dist/ui/components/loading-indicator.js +0 -42
  177. package/dist/ui/components/loading-indicator.js.map +0 -1
  178. package/dist/ui/components/message-display.d.ts +0 -7
  179. package/dist/ui/components/message-display.d.ts.map +0 -1
  180. package/dist/ui/components/message-display.js +0 -104
  181. package/dist/ui/components/message-display.js.map +0 -1
  182. package/dist/ui/components/misc.d.ts +0 -28
  183. package/dist/ui/components/misc.d.ts.map +0 -1
  184. package/dist/ui/components/misc.js +0 -128
  185. package/dist/ui/components/misc.js.map +0 -1
  186. package/dist/ui/components/select-prompt.d.ts +0 -13
  187. package/dist/ui/components/select-prompt.d.ts.map +0 -1
  188. package/dist/ui/components/select-prompt.js +0 -42
  189. package/dist/ui/components/select-prompt.js.map +0 -1
  190. package/dist/ui/components/status-bar.d.ts +0 -11
  191. package/dist/ui/components/status-bar.d.ts.map +0 -1
  192. package/dist/ui/components/status-bar.js +0 -47
  193. package/dist/ui/components/status-bar.js.map +0 -1
  194. package/dist/ui/components/tool-execution.d.ts +0 -3
  195. package/dist/ui/components/tool-execution.d.ts.map +0 -1
  196. package/dist/ui/components/tool-execution.js +0 -374
  197. package/dist/ui/components/tool-execution.js.map +0 -1
  198. package/dist/ui/components/tool-result.d.ts +0 -11
  199. package/dist/ui/components/tool-result.d.ts.map +0 -1
  200. package/dist/ui/components/tool-result.js +0 -58
  201. package/dist/ui/components/tool-result.js.map +0 -1
  202. package/dist/ui/components/welcome-banner.d.ts +0 -3
  203. package/dist/ui/components/welcome-banner.d.ts.map +0 -1
  204. package/dist/ui/components/welcome-banner.js +0 -46
  205. package/dist/ui/components/welcome-banner.js.map +0 -1
  206. package/dist/ui/hooks/useDisplayHistory.d.ts +0 -13
  207. package/dist/ui/hooks/useDisplayHistory.d.ts.map +0 -1
  208. package/dist/ui/hooks/useDisplayHistory.js +0 -45
  209. package/dist/ui/hooks/useDisplayHistory.js.map +0 -1
  210. package/dist/ui/render-engine.d.ts +0 -69
  211. package/dist/ui/render-engine.d.ts.map +0 -1
  212. package/dist/ui/render-engine.js +0 -197
  213. package/dist/ui/render-engine.js.map +0 -1
  214. package/dist/ui/terminal/TerminalRenderer.d.ts +0 -84
  215. package/dist/ui/terminal/TerminalRenderer.d.ts.map +0 -1
  216. package/dist/ui/terminal/TerminalRenderer.js +0 -154
  217. package/dist/ui/terminal/TerminalRenderer.js.map +0 -1
  218. package/dist/ui/terminal/TerminalUI.d.ts +0 -139
  219. package/dist/ui/terminal/TerminalUI.d.ts.map +0 -1
  220. package/dist/ui/terminal/TerminalUI.js +0 -430
  221. package/dist/ui/terminal/TerminalUI.js.map +0 -1
  222. package/dist/ui/terminal/VirtualChatBuffer.d.ts +0 -32
  223. package/dist/ui/terminal/VirtualChatBuffer.d.ts.map +0 -1
  224. package/dist/ui/terminal/VirtualChatBuffer.js +0 -37
  225. package/dist/ui/terminal/VirtualChatBuffer.js.map +0 -1
  226. package/dist/ui/terminal-kit-base.d.ts +0 -117
  227. package/dist/ui/terminal-kit-base.d.ts.map +0 -1
  228. package/dist/ui/terminal-kit-base.js +0 -188
  229. package/dist/ui/terminal-kit-base.js.map +0 -1
  230. package/dist/ui/utils/duplication-detector.d.ts +0 -32
  231. package/dist/ui/utils/duplication-detector.d.ts.map +0 -1
  232. package/dist/ui/utils/duplication-detector.js +0 -227
  233. package/dist/ui/utils/duplication-detector.js.map +0 -1
  234. package/dist/ui/utils/duplication-logger.d.ts +0 -21
  235. package/dist/ui/utils/duplication-logger.d.ts.map +0 -1
  236. package/dist/ui/utils/duplication-logger.js +0 -85
  237. package/dist/ui/utils/duplication-logger.js.map +0 -1
  238. package/dist/ui/utils/terminal-scanner.d.ts +0 -19
  239. package/dist/ui/utils/terminal-scanner.d.ts.map +0 -1
  240. package/dist/ui/utils/terminal-scanner.js +0 -217
  241. package/dist/ui/utils/terminal-scanner.js.map +0 -1
  242. package/dist/version.d.ts +0 -2
  243. package/dist/version.d.ts.map +0 -1
  244. package/dist/version.js +0 -3
  245. package/dist/version.js.map +0 -1
  246. package/scripts/generate-version.js +0 -25
@@ -1,325 +0,0 @@
1
- /**
2
- * Unit tests for AI Context Injector
3
- *
4
- * These tests verify:
5
- * - Context message format
6
- * - Context injection into message history
7
- * - Local context (no injection)
8
- * - Different subshell types (SSH, WSL, Docker)
9
- */
10
- import { AIContextInjector } from '../ai-context-injector.js';
11
- /**
12
- * Test: No injection for local context
13
- */
14
- async function testLocalContextNoInjection() {
15
- console.log('Test: No injection for local context');
16
- const injector = new AIContextInjector();
17
- const messages = [
18
- { role: 'user', content: 'Hello' },
19
- { role: 'assistant', content: 'Hi there!' },
20
- { role: 'user', content: 'List files' },
21
- ];
22
- const localContext = {
23
- type: 'local',
24
- metadata: {
25
- workingDirectory: '/home/user',
26
- shell: 'bash',
27
- os: 'linux',
28
- },
29
- connectionState: 'connected',
30
- sessionId: 'local-session',
31
- };
32
- const result = injector.injectSubshellContext(messages, localContext);
33
- // Should return the same messages without modification
34
- if (result.length !== messages.length) {
35
- throw new Error('Expected no additional messages for local context');
36
- }
37
- if (result !== messages) {
38
- throw new Error('Expected same array reference for local context');
39
- }
40
- console.log('✓ Local context does not inject additional messages');
41
- }
42
- /**
43
- * Test: SSH context injection
44
- */
45
- async function testSSHContextInjection() {
46
- console.log('\nTest: SSH context injection');
47
- const injector = new AIContextInjector();
48
- const messages = [
49
- { role: 'user', content: 'Hello' },
50
- { role: 'assistant', content: 'Hi there!' },
51
- { role: 'user', content: 'List files' },
52
- ];
53
- const sshContext = {
54
- type: 'ssh',
55
- metadata: {
56
- hostname: 'example.com',
57
- username: 'testuser',
58
- workingDirectory: '/home/testuser',
59
- shell: 'bash',
60
- os: 'linux',
61
- port: 22,
62
- },
63
- connectionState: 'connected',
64
- sessionId: 'ssh-session',
65
- };
66
- const result = injector.injectSubshellContext(messages, sshContext);
67
- // Should have one additional message (context message)
68
- if (result.length !== messages.length + 1) {
69
- throw new Error(`Expected ${messages.length + 1} messages, got ${result.length}`);
70
- }
71
- // Context message should be inserted before the last user message
72
- const contextMessage = result[result.length - 2];
73
- if (contextMessage.role !== 'system') {
74
- throw new Error('Expected context message to have role "system"');
75
- }
76
- // Verify context message content
77
- const content = contextMessage.content;
78
- if (!content.includes('SSH')) {
79
- throw new Error('Expected context message to mention SSH');
80
- }
81
- if (!content.includes('example.com')) {
82
- throw new Error('Expected context message to include hostname');
83
- }
84
- if (!content.includes('testuser')) {
85
- throw new Error('Expected context message to include username');
86
- }
87
- if (!content.includes('/home/testuser')) {
88
- throw new Error('Expected context message to include working directory');
89
- }
90
- if (!content.includes('bash')) {
91
- throw new Error('Expected context message to include shell type');
92
- }
93
- if (!content.includes('linux')) {
94
- throw new Error('Expected context message to include OS type');
95
- }
96
- if (!content.includes('22')) {
97
- throw new Error('Expected context message to include port');
98
- }
99
- // Last message should still be the user message
100
- const lastMessage = result[result.length - 1];
101
- if (lastMessage.role !== 'user' || lastMessage.content !== 'List files') {
102
- throw new Error('Expected last message to be the original user message');
103
- }
104
- console.log('✓ SSH context injects correct message with all details');
105
- }
106
- /**
107
- * Test: WSL context injection
108
- */
109
- async function testWSLContextInjection() {
110
- console.log('\nTest: WSL context injection');
111
- const injector = new AIContextInjector();
112
- const messages = [
113
- { role: 'user', content: 'Run command' },
114
- ];
115
- const wslContext = {
116
- type: 'wsl',
117
- metadata: {
118
- distroName: 'Ubuntu-20.04',
119
- workingDirectory: '/home/user',
120
- shell: 'bash',
121
- os: 'linux',
122
- },
123
- connectionState: 'connected',
124
- sessionId: 'wsl-session',
125
- };
126
- const result = injector.injectSubshellContext(messages, wslContext);
127
- // Should have one additional message
128
- if (result.length !== 2) {
129
- throw new Error('Expected 2 messages after injection');
130
- }
131
- const contextMessage = result[0];
132
- if (contextMessage.role !== 'system') {
133
- throw new Error('Expected context message to have role "system"');
134
- }
135
- const content = contextMessage.content;
136
- if (!content.includes('WSL')) {
137
- throw new Error('Expected context message to mention WSL');
138
- }
139
- if (!content.includes('Ubuntu-20.04')) {
140
- throw new Error('Expected context message to include distribution name');
141
- }
142
- console.log('✓ WSL context injects correct message with distribution info');
143
- }
144
- /**
145
- * Test: Docker context injection
146
- */
147
- async function testDockerContextInjection() {
148
- console.log('\nTest: Docker context injection');
149
- const injector = new AIContextInjector();
150
- const messages = [
151
- { role: 'user', content: 'Check container' },
152
- ];
153
- const dockerContext = {
154
- type: 'docker',
155
- metadata: {
156
- containerId: 'abc123def456',
157
- workingDirectory: '/app',
158
- shell: 'sh',
159
- os: 'linux',
160
- },
161
- connectionState: 'connected',
162
- sessionId: 'docker-session',
163
- };
164
- const result = injector.injectSubshellContext(messages, dockerContext);
165
- // Should have one additional message
166
- if (result.length !== 2) {
167
- throw new Error('Expected 2 messages after injection');
168
- }
169
- const contextMessage = result[0];
170
- const content = contextMessage.content;
171
- if (!content.includes('DOCKER')) {
172
- throw new Error('Expected context message to mention DOCKER');
173
- }
174
- if (!content.includes('abc123def456')) {
175
- throw new Error('Expected context message to include container ID');
176
- }
177
- if (!content.includes('/app')) {
178
- throw new Error('Expected context message to include working directory');
179
- }
180
- console.log('✓ Docker context injects correct message with container info');
181
- }
182
- /**
183
- * Test: Context message inserted before last user message
184
- */
185
- async function testContextMessagePosition() {
186
- console.log('\nTest: Context message inserted before last user message');
187
- const injector = new AIContextInjector();
188
- const messages = [
189
- { role: 'user', content: 'First message' },
190
- { role: 'assistant', content: 'First response' },
191
- { role: 'user', content: 'Second message' },
192
- { role: 'assistant', content: 'Second response' },
193
- { role: 'tool', content: 'Tool result' },
194
- { role: 'user', content: 'Third message' },
195
- ];
196
- const sshContext = {
197
- type: 'ssh',
198
- metadata: {
199
- hostname: 'server.com',
200
- username: 'admin',
201
- workingDirectory: '/root',
202
- shell: 'zsh',
203
- os: 'linux',
204
- },
205
- connectionState: 'connected',
206
- sessionId: 'test-session',
207
- };
208
- const result = injector.injectSubshellContext(messages, sshContext);
209
- // Should have 7 messages (6 original + 1 context)
210
- if (result.length !== 7) {
211
- throw new Error(`Expected 7 messages, got ${result.length}`);
212
- }
213
- // Context message should be at index 5 (before last user message at index 6)
214
- const contextMessage = result[5];
215
- if (contextMessage.role !== 'system') {
216
- throw new Error('Expected context message at index 5');
217
- }
218
- // Last message should still be the user message
219
- const lastMessage = result[6];
220
- if (lastMessage.role !== 'user' || lastMessage.content !== 'Third message') {
221
- throw new Error('Expected last message to be the original user message');
222
- }
223
- // Previous messages should be unchanged
224
- if (result[0].content !== 'First message') {
225
- throw new Error('Expected first message to be unchanged');
226
- }
227
- if (result[4].role !== 'tool') {
228
- throw new Error('Expected tool message to remain at correct position');
229
- }
230
- console.log('✓ Context message inserted at correct position');
231
- }
232
- /**
233
- * Test: Context message when no user messages exist
234
- */
235
- async function testContextMessageNoUserMessages() {
236
- console.log('\nTest: Context message when no user messages exist');
237
- const injector = new AIContextInjector();
238
- const messages = [
239
- { role: 'system', content: 'System message' },
240
- { role: 'assistant', content: 'Assistant message' },
241
- ];
242
- const sshContext = {
243
- type: 'ssh',
244
- metadata: {
245
- hostname: 'test.com',
246
- username: 'user',
247
- workingDirectory: '/home/user',
248
- shell: 'bash',
249
- os: 'linux',
250
- },
251
- connectionState: 'connected',
252
- sessionId: 'test-session',
253
- };
254
- const result = injector.injectSubshellContext(messages, sshContext);
255
- // Should append context message to the end
256
- if (result.length !== 3) {
257
- throw new Error('Expected 3 messages');
258
- }
259
- const lastMessage = result[2];
260
- if (lastMessage.role !== 'system') {
261
- throw new Error('Expected context message to be appended');
262
- }
263
- console.log('✓ Context message appended when no user messages exist');
264
- }
265
- /**
266
- * Test: Important warning in context message
267
- */
268
- async function testContextMessageWarning() {
269
- console.log('\nTest: Important warning in context message');
270
- const injector = new AIContextInjector();
271
- const messages = [
272
- { role: 'user', content: 'Test' },
273
- ];
274
- const sshContext = {
275
- type: 'ssh',
276
- metadata: {
277
- hostname: 'remote.com',
278
- username: 'user',
279
- workingDirectory: '/tmp',
280
- shell: 'bash',
281
- os: 'linux',
282
- },
283
- connectionState: 'connected',
284
- sessionId: 'test-session',
285
- };
286
- const result = injector.injectSubshellContext(messages, sshContext);
287
- const contextMessage = result[0];
288
- const content = contextMessage.content;
289
- // Should contain important warning about execution environment
290
- if (!content.includes('IMPORTANT')) {
291
- throw new Error('Expected context message to contain IMPORTANT warning');
292
- }
293
- if (!content.includes('not on the local machine')) {
294
- throw new Error('Expected warning about remote execution');
295
- }
296
- if (!content.includes('ssh environment')) {
297
- throw new Error('Expected mention of ssh environment in warning');
298
- }
299
- console.log('✓ Context message includes important warning');
300
- }
301
- /**
302
- * Run all tests
303
- */
304
- async function runTests() {
305
- console.log('=== AI Context Injector Tests ===\n');
306
- try {
307
- await testLocalContextNoInjection();
308
- await testSSHContextInjection();
309
- await testWSLContextInjection();
310
- await testDockerContextInjection();
311
- await testContextMessagePosition();
312
- await testContextMessageNoUserMessages();
313
- await testContextMessageWarning();
314
- console.log('\n=== All tests passed! ===');
315
- }
316
- catch (error) {
317
- console.error('\n❌ Test failed:', error);
318
- process.exit(1);
319
- }
320
- }
321
- // Run tests if this file is executed directly
322
- if (import.meta.url === `file://${process.argv[1]}`) {
323
- runTests();
324
- }
325
- export { runTests };
@@ -1,96 +0,0 @@
1
- /**
2
- * AI Context Injector
3
- *
4
- * Injects subshell context into AI message history to make the AI aware
5
- * of the current execution environment (SSH, WSL, Docker, etc.)
6
- */
7
- /**
8
- * AI Context Injector class
9
- * Handles injection of subshell context into message history before AI calls
10
- */
11
- export class AIContextInjector {
12
- /**
13
- * Inject subshell context into message history
14
- *
15
- * If the current context is a subshell (not local), this method inserts
16
- * a system message describing the environment before the last user message.
17
- * This ensures the AI is aware of where commands will execute.
18
- *
19
- * @param messages - The conversation history
20
- * @param context - The current subshell context
21
- * @returns Modified message array with context injected (if applicable)
22
- */
23
- injectSubshellContext(messages, context) {
24
- // Don't inject context for local environment
25
- if (context.type === 'local') {
26
- return messages;
27
- }
28
- // Build context message
29
- const contextMessage = {
30
- role: 'system',
31
- content: this.buildContextMessage(context),
32
- };
33
- // Insert context message before the last user message
34
- // This ensures the AI sees the context right before processing the request
35
- const result = [...messages];
36
- // Find the last user message index
37
- let lastUserMessageIndex = -1;
38
- for (let i = result.length - 1; i >= 0; i--) {
39
- if (result[i].role === 'user') {
40
- lastUserMessageIndex = i;
41
- break;
42
- }
43
- }
44
- // If we found a user message, insert context before it
45
- // Otherwise, append to the end
46
- if (lastUserMessageIndex >= 0) {
47
- result.splice(lastUserMessageIndex, 0, contextMessage);
48
- }
49
- else {
50
- result.push(contextMessage);
51
- }
52
- return result;
53
- }
54
- /**
55
- * Build context message describing the current subshell environment
56
- *
57
- * Creates a formatted message that informs the AI about:
58
- * - Environment type (SSH, WSL, Docker)
59
- * - Working directory
60
- * - Shell type
61
- * - Operating system
62
- * - Connection details (hostname, username, etc.)
63
- *
64
- * @param context - The current subshell context
65
- * @returns Formatted context message string
66
- */
67
- buildContextMessage(context) {
68
- const { type, metadata } = context;
69
- let message = `\n\n## CURRENT EXECUTION ENVIRONMENT\n\n`;
70
- message += `You are currently operating in a ${type.toUpperCase()} environment.\n\n`;
71
- message += `**Environment Details:**\n`;
72
- message += `- Type: ${type}\n`;
73
- message += `- Working Directory: ${metadata.workingDirectory}\n`;
74
- message += `- Shell: ${metadata.shell}\n`;
75
- message += `- OS: ${metadata.os}\n`;
76
- // Add type-specific details
77
- if (metadata.hostname) {
78
- message += `- Hostname: ${metadata.hostname}\n`;
79
- }
80
- if (metadata.username) {
81
- message += `- Username: ${metadata.username}\n`;
82
- }
83
- if (metadata.distroName) {
84
- message += `- Distribution: ${metadata.distroName}\n`;
85
- }
86
- if (metadata.containerId) {
87
- message += `- Container: ${metadata.containerId}\n`;
88
- }
89
- if (metadata.port) {
90
- message += `- Port: ${metadata.port}\n`;
91
- }
92
- message += `\n**IMPORTANT:** All commands and file operations you execute will run in this ${type} environment, not on the local machine.\n`;
93
- message += `When reading files, writing files, executing commands, or performing any operations, they will all happen in the ${type} environment.\n`;
94
- return message;
95
- }
96
- }
@@ -1,270 +0,0 @@
1
- /**
2
- * AI Service Client
3
- *
4
- * Handles communication with the backend AI proxy service for streaming
5
- * AI chat requests. Replaces direct Gemini SDK usage in the CLI.
6
- */
7
- import { apiClient } from './api-client.js';
8
- import { readFileSync, existsSync } from 'fs';
9
- import { join } from 'path';
10
- import { homedir } from 'os';
11
- /**
12
- * AI Service Client for streaming chat requests to backend
13
- */
14
- export class AIServiceClient {
15
- constructor() {
16
- this.maxRetries = 3;
17
- this.retryDelay = 1000; // Start with 1 second
18
- // Don't set baseURL yet - lazy load it when first used
19
- // This allows environment variables to be loaded first
20
- this.baseURL = '';
21
- }
22
- /**
23
- * Get the base URL for API requests
24
- * Lazy-loaded to ensure environment variables are loaded first
25
- */
26
- getBaseURL() {
27
- if (!this.baseURL) {
28
- // Use production URL by default, only use localhost in development mode
29
- this.baseURL = process.env.DEV_MODE === 'true'
30
- ? 'http://localhost:3002/api'
31
- : (process.env.BACKEND_URL || 'https://centaurus-backend-354715948975.asia-south1.run.app/api');
32
- }
33
- return this.baseURL;
34
- }
35
- /**
36
- * Stream chat request to backend AI proxy
37
- *
38
- * @param model - The AI model to use (e.g., 'gemini-2.5-flash')
39
- * @param messages - Conversation history including system, user, assistant, and tool messages
40
- * @param tools - Available tool schemas for the AI to use
41
- * @param environmentContext - Optional environment context (OS, shell, cwd, etc.)
42
- * @param mode - Optional mode (default, plan, command)
43
- * @yields Stream chunks (text, tool calls, done, or error events)
44
- */
45
- async *streamChat(model, messages, tools, environmentContext, mode) {
46
- // Build request payload
47
- const payload = {
48
- model,
49
- messages,
50
- tools,
51
- stream: true,
52
- environmentContext,
53
- mode,
54
- };
55
- // Get authentication token from api client
56
- if (!apiClient.isAuthenticated()) {
57
- yield {
58
- type: 'error',
59
- message: 'Authentication required. Please sign in.',
60
- code: 'AUTH_REQUIRED',
61
- };
62
- return;
63
- }
64
- // Retry logic for transient errors
65
- let lastError = null;
66
- for (let attempt = 0; attempt < this.maxRetries; attempt++) {
67
- try {
68
- // Make fetch request to backend AI endpoint
69
- const response = await fetch(`${this.getBaseURL()}/ai/chat`, {
70
- method: 'POST',
71
- headers: {
72
- 'Content-Type': 'application/json',
73
- 'Authorization': `Bearer ${this.getSessionToken()}`,
74
- },
75
- body: JSON.stringify(payload),
76
- signal: AbortSignal.timeout(60000), // 60 second timeout
77
- });
78
- // Check for HTTP errors
79
- if (!response.ok) {
80
- const errorText = await response.text();
81
- let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
82
- let errorCode = 'HTTP_ERROR';
83
- try {
84
- const errorData = JSON.parse(errorText);
85
- if (errorData.error) {
86
- errorMessage = errorData.error.message || errorMessage;
87
- errorCode = errorData.error.code || errorCode;
88
- }
89
- }
90
- catch {
91
- // If response is not JSON, use the text as message
92
- if (errorText) {
93
- errorMessage = errorText;
94
- }
95
- }
96
- // Handle specific error codes
97
- if (response.status === 401) {
98
- yield {
99
- type: 'error',
100
- message: 'Session expired. Please sign in again.',
101
- code: 'AUTH_REQUIRED',
102
- };
103
- return;
104
- }
105
- if (response.status === 429) {
106
- // Rate limit - don't retry immediately
107
- yield {
108
- type: 'error',
109
- message: 'Service temporarily unavailable due to high demand. Please try again in a moment.',
110
- code: 'RATE_LIMIT',
111
- };
112
- return;
113
- }
114
- if (response.status === 504) {
115
- // Timeout error - retryable
116
- lastError = {
117
- type: 'error',
118
- message: 'Request timed out. Retrying...',
119
- code: 'TIMEOUT',
120
- };
121
- if (attempt < this.maxRetries - 1) {
122
- await this.sleep(this.retryDelay * Math.pow(2, attempt));
123
- continue;
124
- }
125
- }
126
- // For other errors, yield and return
127
- yield {
128
- type: 'error',
129
- message: errorMessage,
130
- code: errorCode,
131
- };
132
- return;
133
- }
134
- // Check if response body exists
135
- if (!response.body) {
136
- yield {
137
- type: 'error',
138
- message: 'No response body from backend',
139
- code: 'NO_RESPONSE_BODY',
140
- };
141
- return;
142
- }
143
- // Parse SSE stream - if successful, we're done
144
- yield* this.parseSSEStream(response.body);
145
- return;
146
- }
147
- catch (error) {
148
- // Handle network errors
149
- if (error.name === 'TypeError' && error.message.includes('fetch')) {
150
- lastError = {
151
- type: 'error',
152
- message: 'Backend service is unreachable. Please check your connection.',
153
- code: 'NETWORK_ERROR',
154
- };
155
- }
156
- else if (error.name === 'AbortError' || error.name === 'TimeoutError') {
157
- lastError = {
158
- type: 'error',
159
- message: 'Request timed out. Please try again.',
160
- code: 'TIMEOUT',
161
- };
162
- }
163
- else {
164
- lastError = {
165
- type: 'error',
166
- message: error.message || 'Unknown error occurred',
167
- code: error.code || 'UNKNOWN_ERROR',
168
- };
169
- }
170
- // Retry for transient errors
171
- if (this.isRetryableError(lastError.code) && attempt < this.maxRetries - 1) {
172
- await this.sleep(this.retryDelay * Math.pow(2, attempt));
173
- continue;
174
- }
175
- // If not retryable or max retries reached, yield error and return
176
- break;
177
- }
178
- }
179
- // If we get here, we've exhausted retries
180
- if (lastError) {
181
- yield lastError;
182
- }
183
- }
184
- /**
185
- * Check if an error code is retryable
186
- */
187
- isRetryableError(code) {
188
- const retryableCodes = ['NETWORK_ERROR', 'TIMEOUT', 'UNKNOWN_ERROR'];
189
- return retryableCodes.includes(code);
190
- }
191
- /**
192
- * Sleep for specified milliseconds
193
- */
194
- sleep(ms) {
195
- return new Promise(resolve => setTimeout(resolve, ms));
196
- }
197
- /**
198
- * Get session token from apiClient
199
- * This is a workaround since sessionToken is private
200
- */
201
- getSessionToken() {
202
- // Read session token from the same location apiClient uses
203
- const configPath = join(homedir(), '.centaurus', 'session.json');
204
- try {
205
- if (existsSync(configPath)) {
206
- const data = readFileSync(configPath, 'utf-8');
207
- const session = JSON.parse(data);
208
- return session.sessionToken || '';
209
- }
210
- }
211
- catch (error) {
212
- // Return empty string if unable to read
213
- }
214
- return '';
215
- }
216
- /**
217
- * Parse Server-Sent Events stream from response body
218
- *
219
- * @param body - ReadableStream from fetch response
220
- * @yields Parsed stream chunks
221
- */
222
- async *parseSSEStream(body) {
223
- const reader = body.getReader();
224
- const decoder = new TextDecoder();
225
- let buffer = '';
226
- try {
227
- while (true) {
228
- const { done, value } = await reader.read();
229
- if (done) {
230
- break;
231
- }
232
- // Decode chunk and add to buffer
233
- buffer += decoder.decode(value, { stream: true });
234
- // Process complete lines in buffer
235
- const lines = buffer.split('\n');
236
- // Keep the last incomplete line in buffer
237
- buffer = lines.pop() || '';
238
- for (const line of lines) {
239
- // SSE format: "data: {json}"
240
- if (line.startsWith('data: ')) {
241
- const dataStr = line.slice(6); // Remove "data: " prefix
242
- // Skip empty data lines
243
- if (!dataStr.trim()) {
244
- continue;
245
- }
246
- try {
247
- const chunk = JSON.parse(dataStr);
248
- yield chunk;
249
- // Stop if we receive a done or error event
250
- if (chunk.type === 'done' || chunk.type === 'error') {
251
- return;
252
- }
253
- }
254
- catch (error) {
255
- // Skip malformed JSON
256
- console.error('Failed to parse SSE data:', dataStr);
257
- }
258
- }
259
- // SSE event type line: "event: chunk"
260
- // We don't need to process these separately since the data contains the type
261
- }
262
- }
263
- }
264
- finally {
265
- reader.releaseLock();
266
- }
267
- }
268
- }
269
- // Export singleton instance
270
- export const aiServiceClient = new AIServiceClient();