@abacus-ai/cli 2.0.0-canary.1 → 2.0.0-canary.11

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 (198) hide show
  1. package/README.md +25 -0
  2. package/dist/index.mjs +466 -438
  3. package/package.json +4 -1
  4. package/.oxlintrc.json +0 -8
  5. package/resources/abacus.ico +0 -0
  6. package/resources/entitlements.plist +0 -9
  7. package/src/__e2e__/README.md +0 -196
  8. package/src/__e2e__/agent-interactions.e2e.test.tsx +0 -61
  9. package/src/__e2e__/cli-commands.e2e.test.tsx +0 -77
  10. package/src/__e2e__/conversation-throttle.e2e.test.ts +0 -453
  11. package/src/__e2e__/conversation.e2e.test.tsx +0 -56
  12. package/src/__e2e__/diff-preview.e2e.test.tsx +0 -3399
  13. package/src/__e2e__/file-creation.e2e.test.tsx +0 -149
  14. package/src/__e2e__/helpers/test-helpers.ts +0 -449
  15. package/src/__e2e__/keyboard-navigation.e2e.test.tsx +0 -34
  16. package/src/__e2e__/llm-models.e2e.test.ts +0 -402
  17. package/src/__e2e__/mcp/mcp-callback-flow.e2e.test.tsx +0 -71
  18. package/src/__e2e__/mcp/mcp-full-app-ui.e2e.test.tsx +0 -167
  19. package/src/__e2e__/mcp/mcp-ui-rendering.e2e.test.tsx +0 -185
  20. package/src/__e2e__/repl.e2e.test.tsx +0 -78
  21. package/src/__e2e__/shell-compatibility.e2e.test.tsx +0 -76
  22. package/src/__e2e__/theme-mcp.e2e.test.tsx +0 -98
  23. package/src/__e2e__/tool-permissions.e2e.test.tsx +0 -66
  24. package/src/args.ts +0 -22
  25. package/src/components/__tests__/react-compiler.test.tsx +0 -78
  26. package/src/components/__tests__/status-indicator.test.tsx +0 -403
  27. package/src/components/composer/__tests__/bash-runner.test.tsx +0 -263
  28. package/src/components/composer/agent-mode-indicator.tsx +0 -63
  29. package/src/components/composer/bash-runner.tsx +0 -54
  30. package/src/components/composer/commands/default-commands.tsx +0 -615
  31. package/src/components/composer/commands/handler.tsx +0 -59
  32. package/src/components/composer/commands/picker.tsx +0 -273
  33. package/src/components/composer/commands/registry.ts +0 -233
  34. package/src/components/composer/commands/types.ts +0 -33
  35. package/src/components/composer/context.tsx +0 -88
  36. package/src/components/composer/file-mention-picker.tsx +0 -83
  37. package/src/components/composer/help.tsx +0 -44
  38. package/src/components/composer/index.tsx +0 -1007
  39. package/src/components/composer/mentions.ts +0 -57
  40. package/src/components/composer/message-queue.tsx +0 -70
  41. package/src/components/composer/mode-panel.tsx +0 -35
  42. package/src/components/composer/modes/__tests__/bash-handler.test.tsx +0 -755
  43. package/src/components/composer/modes/__tests__/bash-renderer.test.tsx +0 -1108
  44. package/src/components/composer/modes/bash-handler.tsx +0 -132
  45. package/src/components/composer/modes/bash-renderer.tsx +0 -175
  46. package/src/components/composer/modes/default-handlers.tsx +0 -33
  47. package/src/components/composer/modes/index.ts +0 -41
  48. package/src/components/composer/modes/types.ts +0 -21
  49. package/src/components/composer/persistent-shell.ts +0 -283
  50. package/src/components/composer/process.ts +0 -65
  51. package/src/components/composer/types.ts +0 -9
  52. package/src/components/composer/use-mention-search.ts +0 -68
  53. package/src/components/error-boundry.tsx +0 -60
  54. package/src/components/exit-message.tsx +0 -29
  55. package/src/components/expanded-view.tsx +0 -74
  56. package/src/components/file-completion.tsx +0 -127
  57. package/src/components/header.tsx +0 -47
  58. package/src/components/logo.tsx +0 -37
  59. package/src/components/segments.tsx +0 -356
  60. package/src/components/status-indicator.tsx +0 -306
  61. package/src/components/tool-group-summary.tsx +0 -263
  62. package/src/components/tool-permissions/ask-user-question-permission-ui.tsx +0 -319
  63. package/src/components/tool-permissions/diff-preview.tsx +0 -359
  64. package/src/components/tool-permissions/index.ts +0 -5
  65. package/src/components/tool-permissions/permission-options.tsx +0 -401
  66. package/src/components/tool-permissions/permission-preview-header.tsx +0 -57
  67. package/src/components/tool-permissions/tool-permission-ui.tsx +0 -420
  68. package/src/components/tools/agent/ask-user-question.tsx +0 -107
  69. package/src/components/tools/agent/enter-plan-mode.tsx +0 -55
  70. package/src/components/tools/agent/exit-plan-mode.tsx +0 -83
  71. package/src/components/tools/agent/handoff-to-main.tsx +0 -27
  72. package/src/components/tools/agent/subagent.tsx +0 -37
  73. package/src/components/tools/agent/todo-write.tsx +0 -104
  74. package/src/components/tools/browser/close-tab.tsx +0 -58
  75. package/src/components/tools/browser/computer.tsx +0 -70
  76. package/src/components/tools/browser/get-interactive-elements.tsx +0 -54
  77. package/src/components/tools/browser/get-tab-content.tsx +0 -51
  78. package/src/components/tools/browser/navigate-to.tsx +0 -59
  79. package/src/components/tools/browser/new-tab.tsx +0 -60
  80. package/src/components/tools/browser/perform-action.tsx +0 -63
  81. package/src/components/tools/browser/refresh-tab.tsx +0 -43
  82. package/src/components/tools/browser/switch-tab.tsx +0 -58
  83. package/src/components/tools/filesystem/delete-file.tsx +0 -104
  84. package/src/components/tools/filesystem/edit.tsx +0 -220
  85. package/src/components/tools/filesystem/list-dir.tsx +0 -78
  86. package/src/components/tools/filesystem/read-file.tsx +0 -180
  87. package/src/components/tools/filesystem/upload-image.tsx +0 -76
  88. package/src/components/tools/ide/ide-diagnostics.tsx +0 -62
  89. package/src/components/tools/index.ts +0 -91
  90. package/src/components/tools/mcp/mcp-tool.tsx +0 -158
  91. package/src/components/tools/search/fetch-url.tsx +0 -73
  92. package/src/components/tools/search/file-search.tsx +0 -78
  93. package/src/components/tools/search/grep.tsx +0 -90
  94. package/src/components/tools/search/semantic-search.tsx +0 -66
  95. package/src/components/tools/search/web-search.tsx +0 -71
  96. package/src/components/tools/shared/index.tsx +0 -48
  97. package/src/components/tools/shared/zod-coercion.ts +0 -35
  98. package/src/components/tools/terminal/bash-tool-output.tsx +0 -188
  99. package/src/components/tools/terminal/get-terminal-output.tsx +0 -91
  100. package/src/components/tools/terminal/run-in-terminal.tsx +0 -131
  101. package/src/components/tools/types.ts +0 -16
  102. package/src/components/tools.tsx +0 -68
  103. package/src/components/ui/__tests__/divider.test.tsx +0 -61
  104. package/src/components/ui/__tests__/gradient.test.tsx +0 -125
  105. package/src/components/ui/__tests__/input.test.tsx +0 -166
  106. package/src/components/ui/__tests__/select.test.tsx +0 -273
  107. package/src/components/ui/__tests__/shimmer.test.tsx +0 -99
  108. package/src/components/ui/blinking-indicator.tsx +0 -27
  109. package/src/components/ui/divider.tsx +0 -162
  110. package/src/components/ui/gradient.tsx +0 -56
  111. package/src/components/ui/input.tsx +0 -228
  112. package/src/components/ui/select.tsx +0 -151
  113. package/src/components/ui/shimmer.tsx +0 -76
  114. package/src/context/agent-mode.tsx +0 -95
  115. package/src/context/extension-file.tsx +0 -136
  116. package/src/context/network-activity.tsx +0 -45
  117. package/src/context/notification.tsx +0 -62
  118. package/src/context/shell-size.tsx +0 -49
  119. package/src/context/shell-title.tsx +0 -38
  120. package/src/entrypoints/print-mode.ts +0 -312
  121. package/src/entrypoints/repl.tsx +0 -389
  122. package/src/hooks/use-agent.ts +0 -15
  123. package/src/hooks/use-api-client.ts +0 -1
  124. package/src/hooks/use-available-height.ts +0 -8
  125. package/src/hooks/use-cleanup.ts +0 -29
  126. package/src/hooks/use-interrupt-manager.ts +0 -242
  127. package/src/hooks/use-models.ts +0 -22
  128. package/src/index.ts +0 -217
  129. package/src/lib/__tests__/ansi.test.ts +0 -255
  130. package/src/lib/__tests__/cli.test.ts +0 -122
  131. package/src/lib/__tests__/commands.test.ts +0 -325
  132. package/src/lib/__tests__/constants.test.ts +0 -15
  133. package/src/lib/__tests__/focusables.test.ts +0 -25
  134. package/src/lib/__tests__/fs.test.ts +0 -231
  135. package/src/lib/__tests__/markdown.test.tsx +0 -348
  136. package/src/lib/__tests__/mcpCommandHandler.test.ts +0 -173
  137. package/src/lib/__tests__/mcpManagement.test.ts +0 -38
  138. package/src/lib/__tests__/path-paste.test.ts +0 -144
  139. package/src/lib/__tests__/path.test.ts +0 -300
  140. package/src/lib/__tests__/queries.test.ts +0 -39
  141. package/src/lib/__tests__/standaloneMcpService.test.ts +0 -71
  142. package/src/lib/__tests__/text-buffer.test.ts +0 -328
  143. package/src/lib/__tests__/text-utils.test.ts +0 -32
  144. package/src/lib/__tests__/timing.test.ts +0 -78
  145. package/src/lib/__tests__/utils.test.ts +0 -238
  146. package/src/lib/__tests__/vim-buffer-actions.test.ts +0 -154
  147. package/src/lib/ansi.ts +0 -150
  148. package/src/lib/cli-push-server.ts +0 -112
  149. package/src/lib/cli.ts +0 -44
  150. package/src/lib/clipboard.ts +0 -226
  151. package/src/lib/command-utils.ts +0 -93
  152. package/src/lib/commands.ts +0 -270
  153. package/src/lib/constants.ts +0 -3
  154. package/src/lib/extension-connection.ts +0 -181
  155. package/src/lib/focusables.ts +0 -7
  156. package/src/lib/fs.ts +0 -533
  157. package/src/lib/markdown/code-block.tsx +0 -63
  158. package/src/lib/markdown/index.ts +0 -4
  159. package/src/lib/markdown/link.tsx +0 -19
  160. package/src/lib/markdown/markdown.tsx +0 -372
  161. package/src/lib/markdown/types.ts +0 -15
  162. package/src/lib/mcpCommandHandler.ts +0 -121
  163. package/src/lib/mcpManagement.ts +0 -44
  164. package/src/lib/path-paste.ts +0 -185
  165. package/src/lib/path.ts +0 -179
  166. package/src/lib/queries.ts +0 -15
  167. package/src/lib/standaloneMcpService.ts +0 -688
  168. package/src/lib/status-utils.ts +0 -237
  169. package/src/lib/test-utils.tsx +0 -72
  170. package/src/lib/text-buffer.ts +0 -2415
  171. package/src/lib/text-utils.ts +0 -272
  172. package/src/lib/timing.ts +0 -63
  173. package/src/lib/types.ts +0 -295
  174. package/src/lib/utils.ts +0 -182
  175. package/src/lib/vim-buffer-actions.ts +0 -732
  176. package/src/providers/agent.tsx +0 -1063
  177. package/src/providers/api-client.tsx +0 -43
  178. package/src/services/logger.ts +0 -85
  179. package/src/terminal/detection.ts +0 -187
  180. package/src/terminal/exit.ts +0 -279
  181. package/src/terminal/notification.ts +0 -83
  182. package/src/terminal/progress.ts +0 -201
  183. package/src/terminal/setup.ts +0 -797
  184. package/src/terminal/types.ts +0 -51
  185. package/src/theme/context.tsx +0 -57
  186. package/src/theme/index.ts +0 -4
  187. package/src/theme/themed.tsx +0 -35
  188. package/src/theme/themes.json +0 -546
  189. package/src/theme/types.ts +0 -110
  190. package/src/tools/types.ts +0 -59
  191. package/src/tools/utils/__tests__/zod-coercion.test.ts +0 -33
  192. package/src/tools/utils/tool-ui-components.tsx +0 -649
  193. package/src/tools/utils/zod-coercion.ts +0 -35
  194. package/tsconfig.json +0 -16
  195. package/tsconfig.node.json +0 -29
  196. package/tsconfig.test.json +0 -27
  197. package/tsdown.config.ts +0 -17
  198. package/vitest.config.ts +0 -76
@@ -1,403 +0,0 @@
1
- // @vitest-environment node
2
-
3
- import stripAnsi from "strip-ansi";
4
- import { describe, it, expect, afterEach } from "vitest";
5
-
6
- import { NetworkActivityProvider } from "../../context/network-activity.js";
7
- import { render, logInk, cleanup } from "../../lib/test-utils.js";
8
- import { AgentStatus } from "../../providers/agent.js";
9
- import { StatusIndicator } from "../status-indicator.js";
10
-
11
- // Helper function to wrap StatusIndicator with required providers
12
- function renderStatusIndicator(status: AgentStatus) {
13
- return render(
14
- <NetworkActivityProvider>
15
- <StatusIndicator status={status} />
16
- </NetworkActivityProvider>,
17
- );
18
- }
19
-
20
- describe.concurrent("StatusIndicator", () => {
21
- afterEach(() => {
22
- cleanup();
23
- });
24
-
25
- it("should not render when status is Idle", () => {
26
- const instance = renderStatusIndicator(AgentStatus.Idle);
27
-
28
- logInk(instance);
29
-
30
- const output = instance.lastFrame() ?? "";
31
- // Should be empty or whitespace only when idle
32
- expect(stripAnsi(output).trim()).toBe("");
33
- });
34
-
35
- it("should not render when status is WaitingForToolPermission", () => {
36
- const instance = renderStatusIndicator(AgentStatus.WaitingForToolPermission);
37
-
38
- logInk(instance);
39
-
40
- const output = instance.lastFrame() ?? "";
41
- expect(stripAnsi(output).trim()).toBe("");
42
- });
43
-
44
- it("should render shimmer text when status is Submitted", () => {
45
- const instance = renderStatusIndicator(AgentStatus.Submitted);
46
-
47
- logInk(instance);
48
-
49
- const output = instance.lastFrame() ?? "";
50
- const plainText = stripAnsi(output);
51
-
52
- expect(output).toBeDefined();
53
- // Should show some status text with "..." and escape hint
54
- expect(plainText).toContain("...");
55
- expect(plainText).toContain("esc to interrupt");
56
- });
57
-
58
- it("should render shimmer text when status is Streaming", () => {
59
- const instance = renderStatusIndicator(AgentStatus.Streaming);
60
-
61
- logInk(instance);
62
-
63
- const output = instance.lastFrame() ?? "";
64
- const plainText = stripAnsi(output);
65
-
66
- expect(output).toBeDefined();
67
- expect(plainText).toContain("...");
68
- expect(plainText).toContain("esc to interrupt");
69
- });
70
-
71
- it("should render shimmer text when status is ExecutingTool", () => {
72
- const instance = renderStatusIndicator(AgentStatus.ExecutingTool);
73
-
74
- logInk(instance);
75
-
76
- const output = instance.lastFrame() ?? "";
77
- const plainText = stripAnsi(output);
78
-
79
- expect(output).toBeDefined();
80
- expect(plainText).toContain("...");
81
- expect(plainText).toContain("esc to interrupt");
82
- });
83
-
84
- it('should render "Resuming" when status is LoadingConversation', () => {
85
- const instance = renderStatusIndicator(AgentStatus.LoadingConversation);
86
-
87
- logInk(instance);
88
-
89
- const output = instance.lastFrame() ?? "";
90
- const plainText = stripAnsi(output);
91
-
92
- expect(output).toBeDefined();
93
- expect(plainText).toContain("Resuming...");
94
- // Should NOT show escape hint for loading conversation
95
- expect(plainText).not.toContain("esc to interrupt");
96
- });
97
-
98
- it("should show indicator when transitioning from Idle to ExecutingTool", () => {
99
- // This simulates what happens when an MCP tool starts executing
100
- // Using rerender() to simulate prop changes from parent (AgentProvider)
101
- const instance = renderStatusIndicator(AgentStatus.Idle);
102
-
103
- // Initially idle - should be empty
104
- let output = instance.lastFrame() ?? "";
105
- expect(stripAnsi(output).trim()).toBe("");
106
-
107
- // Simulate MCP tool starting - parent updates status prop
108
- instance.rerender(
109
- <NetworkActivityProvider>
110
- <StatusIndicator status={AgentStatus.ExecutingTool} />
111
- </NetworkActivityProvider>,
112
- );
113
-
114
- // Re-render should show the indicator
115
- output = instance.lastFrame() ?? "";
116
- const plainText = stripAnsi(output);
117
-
118
- console.log("[UI Test] After status change to ExecutingTool:", plainText);
119
-
120
- expect(plainText).toContain("...");
121
- expect(plainText).toContain("esc to interrupt");
122
- });
123
-
124
- it("should hide indicator when transitioning from ExecutingTool to Idle", () => {
125
- // This simulates what happens when an MCP tool finishes executing
126
- const instance = renderStatusIndicator(AgentStatus.ExecutingTool);
127
-
128
- // Initially executing - should show indicator
129
- let output = instance.lastFrame() ?? "";
130
- let plainText = stripAnsi(output);
131
- expect(plainText).toContain("...");
132
-
133
- // Simulate MCP tool completing - parent updates status prop
134
- instance.rerender(
135
- <NetworkActivityProvider>
136
- <StatusIndicator status={AgentStatus.Idle} />
137
- </NetworkActivityProvider>,
138
- );
139
-
140
- // Re-render should hide the indicator
141
- output = instance.lastFrame() ?? "";
142
- plainText = stripAnsi(output);
143
-
144
- console.log("[UI Test] After status change to Idle:", `"${plainText}"`);
145
-
146
- expect(plainText.trim()).toBe("");
147
- });
148
-
149
- it("should update immediately when status changes (simulating MCP tool call)", () => {
150
- // This test verifies that UI updates happen synchronously with status changes
151
- // If there was a delay bug, this test would catch it
152
- const instance = renderStatusIndicator(AgentStatus.Idle);
153
-
154
- const startTime = Date.now();
155
-
156
- // Simulate rapid status changes like during MCP tool execution
157
- instance.rerender(
158
- <NetworkActivityProvider>
159
- <StatusIndicator status={AgentStatus.Submitted} />
160
- </NetworkActivityProvider>,
161
- );
162
- const afterSubmitted = instance.lastFrame() ?? "";
163
-
164
- instance.rerender(
165
- <NetworkActivityProvider>
166
- <StatusIndicator status={AgentStatus.ExecutingTool} />
167
- </NetworkActivityProvider>,
168
- );
169
- const afterExecuting = instance.lastFrame() ?? "";
170
-
171
- instance.rerender(
172
- <NetworkActivityProvider>
173
- <StatusIndicator status={AgentStatus.Streaming} />
174
- </NetworkActivityProvider>,
175
- );
176
- const afterStreaming = instance.lastFrame() ?? "";
177
-
178
- instance.rerender(
179
- <NetworkActivityProvider>
180
- <StatusIndicator status={AgentStatus.Idle} />
181
- </NetworkActivityProvider>,
182
- );
183
- const afterIdle = instance.lastFrame() ?? "";
184
-
185
- const totalTime = Date.now() - startTime;
186
-
187
- console.log("[UI Test] Status transitions:");
188
- console.log(" - Submitted:", stripAnsi(afterSubmitted).includes("...") ? "visible" : "hidden");
189
- console.log(
190
- " - ExecutingTool:",
191
- stripAnsi(afterExecuting).includes("...") ? "visible" : "hidden",
192
- );
193
- console.log(" - Streaming:", stripAnsi(afterStreaming).includes("...") ? "visible" : "hidden");
194
- console.log(" - Idle:", stripAnsi(afterIdle).trim() === "" ? "hidden" : "visible");
195
- console.log(` Total time for 4 transitions: ${totalTime}ms`);
196
-
197
- // Verify each state rendered correctly
198
- expect(stripAnsi(afterSubmitted)).toContain("...");
199
- expect(stripAnsi(afterExecuting)).toContain("...");
200
- expect(stripAnsi(afterStreaming)).toContain("...");
201
- expect(stripAnsi(afterIdle).trim()).toBe("");
202
-
203
- // All transitions should be nearly instant (< 50ms total for 4 changes)
204
- // This catches bugs where UI updates are delayed
205
- expect(totalTime).toBeLessThan(50);
206
- });
207
- });
208
-
209
- describe.sequential("StatusIndicator - MCP Tool Execution Flow", () => {
210
- afterEach(() => {
211
- cleanup();
212
- });
213
-
214
- /**
215
- * This test simulates the exact flow that happens when an MCP tool is called:
216
- * 1. User sends message -> Submitted
217
- * 2. Agent starts processing -> Streaming
218
- * 3. Agent calls MCP tool -> ExecutingTool
219
- * 4. Tool completes -> Streaming (continues)
220
- * 5. Agent done -> Idle
221
- *
222
- * The bug your teammate fixed was that step 3 (ExecutingTool) wasn't showing
223
- * in the UI because status updates weren't being handled correctly.
224
- */
225
- it("should show correct UI through full MCP tool execution lifecycle", async () => {
226
- const renderTimes: { status: string; frame: string; time: number; visible: boolean }[] = [];
227
- const startTime = Date.now();
228
-
229
- const instance = renderStatusIndicator(AgentStatus.Idle);
230
-
231
- // Step 0: Initial idle state
232
- let frame = instance.lastFrame() ?? "";
233
- renderTimes.push({
234
- status: "Idle (initial)",
235
- frame: stripAnsi(frame),
236
- time: Date.now() - startTime,
237
- visible: stripAnsi(frame).includes("..."),
238
- });
239
-
240
- // Step 1: User submits message
241
- instance.rerender(
242
- <NetworkActivityProvider>
243
- <StatusIndicator status={AgentStatus.Submitted} />
244
- </NetworkActivityProvider>,
245
- );
246
- frame = instance.lastFrame() ?? "";
247
- renderTimes.push({
248
- status: "Submitted",
249
- frame: stripAnsi(frame),
250
- time: Date.now() - startTime,
251
- visible: stripAnsi(frame).includes("..."),
252
- });
253
-
254
- // Small delay to simulate async behavior
255
- await new Promise((r) => setTimeout(r, 10));
256
-
257
- // Step 2: Agent starts streaming response
258
- instance.rerender(
259
- <NetworkActivityProvider>
260
- <StatusIndicator status={AgentStatus.Streaming} />
261
- </NetworkActivityProvider>,
262
- );
263
- frame = instance.lastFrame() ?? "";
264
- renderTimes.push({
265
- status: "Streaming",
266
- frame: stripAnsi(frame),
267
- time: Date.now() - startTime,
268
- visible: stripAnsi(frame).includes("..."),
269
- });
270
-
271
- await new Promise((r) => setTimeout(r, 10));
272
-
273
- // Step 3: Agent calls MCP tool - THIS IS THE CRITICAL PART
274
- // If status update is delayed, user sees no feedback
275
- instance.rerender(
276
- <NetworkActivityProvider>
277
- <StatusIndicator status={AgentStatus.ExecutingTool} />
278
- </NetworkActivityProvider>,
279
- );
280
- frame = instance.lastFrame() ?? "";
281
- renderTimes.push({
282
- status: "ExecutingTool",
283
- frame: stripAnsi(frame),
284
- time: Date.now() - startTime,
285
- visible: stripAnsi(frame).includes("..."),
286
- });
287
-
288
- await new Promise((r) => setTimeout(r, 10));
289
-
290
- // Step 4: Tool completes, back to streaming
291
- instance.rerender(
292
- <NetworkActivityProvider>
293
- <StatusIndicator status={AgentStatus.Streaming} />
294
- </NetworkActivityProvider>,
295
- );
296
- frame = instance.lastFrame() ?? "";
297
- renderTimes.push({
298
- status: "Streaming (after tool)",
299
- frame: stripAnsi(frame),
300
- time: Date.now() - startTime,
301
- visible: stripAnsi(frame).includes("..."),
302
- });
303
-
304
- await new Promise((r) => setTimeout(r, 10));
305
-
306
- // Step 5: Agent completes
307
- instance.rerender(
308
- <NetworkActivityProvider>
309
- <StatusIndicator status={AgentStatus.Idle} />
310
- </NetworkActivityProvider>,
311
- );
312
- frame = instance.lastFrame() ?? "";
313
- renderTimes.push({
314
- status: "Idle (final)",
315
- frame: stripAnsi(frame),
316
- time: Date.now() - startTime,
317
- visible: stripAnsi(frame).includes("..."),
318
- });
319
-
320
- // Log the timeline
321
- console.log("[MCP Flow Test] Render timeline:");
322
- renderTimes.forEach((r) => {
323
- console.log(` ${r.time}ms: ${r.status} -> ${r.visible ? "VISIBLE" : "hidden"}`);
324
- });
325
-
326
- // Verify we got through all states
327
- expect(renderTimes.length).toBe(6);
328
-
329
- // Verify the ExecutingTool state was visible (the bug was this not showing)
330
- const executingToolEntry = renderTimes.find((r) => r.status === "ExecutingTool");
331
- expect(executingToolEntry).toBeDefined();
332
- expect(executingToolEntry?.visible).toBe(true);
333
-
334
- // Verify Submitted was visible
335
- const submittedEntry = renderTimes.find((r) => r.status === "Submitted");
336
- expect(submittedEntry?.visible).toBe(true);
337
-
338
- // Verify Streaming was visible
339
- const streamingEntry = renderTimes.find((r) => r.status === "Streaming");
340
- expect(streamingEntry?.visible).toBe(true);
341
-
342
- // Verify final Idle is hidden
343
- const idleFinalEntry = renderTimes.find((r) => r.status === "Idle (final)");
344
- expect(idleFinalEntry?.visible).toBe(false);
345
-
346
- // Verify timing - each transition should happen quickly
347
- for (let i = 1; i < renderTimes.length; i++) {
348
- const timeBetween = renderTimes[i].time - renderTimes[i - 1].time;
349
- // Each transition should be < 20ms (plus our 10ms simulated delay)
350
- expect(timeBetween).toBeLessThanOrEqual(40);
351
- }
352
- });
353
-
354
- /**
355
- * Test that verifies UI responds to status changes without artificial delays.
356
- * This would catch bugs where status updates are batched or delayed.
357
- */
358
- it("should render each status change immediately without batching", () => {
359
- const instance = renderStatusIndicator(AgentStatus.Idle);
360
- const frames: string[] = [];
361
-
362
- // Rapid-fire status changes
363
- const statuses: AgentStatus[] = [
364
- AgentStatus.Submitted,
365
- AgentStatus.Streaming,
366
- AgentStatus.ExecutingTool,
367
- AgentStatus.WaitingForToolPermission,
368
- AgentStatus.ExecutingTool,
369
- AgentStatus.Streaming,
370
- AgentStatus.Idle,
371
- ];
372
-
373
- const startTime = Date.now();
374
-
375
- for (const status of statuses) {
376
- instance.rerender(
377
- <NetworkActivityProvider>
378
- <StatusIndicator status={status} />
379
- </NetworkActivityProvider>,
380
- );
381
- frames.push(instance.lastFrame() ?? "");
382
- }
383
-
384
- const totalTime = Date.now() - startTime;
385
-
386
- console.log(`[Batching Test] ${statuses.length} status changes in ${totalTime}ms`);
387
- console.log("[Batching Test] Frames captured:", frames.length);
388
-
389
- // Each status change should produce a frame
390
- expect(frames.length).toBe(statuses.length);
391
-
392
- // Should be very fast (no delays between renders)
393
- expect(totalTime).toBeLessThan(50);
394
-
395
- // Verify specific states rendered correctly
396
- // Submitted (index 0) should be visible
397
- expect(stripAnsi(frames[0])).toContain("...");
398
- // WaitingForToolPermission (index 3) should be hidden
399
- expect(stripAnsi(frames[3]).trim()).toBe("");
400
- // Final Idle (index 6) should be hidden
401
- expect(stripAnsi(frames[6]).trim()).toBe("");
402
- });
403
- });
@@ -1,263 +0,0 @@
1
- import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
2
-
3
- import * as pathModule from "../../../lib/path.js";
4
- import { render, cleanup, logInk } from "../../../lib/test-utils.js";
5
- import { BashRunner } from "../bash-runner.js";
6
-
7
- // Mock the path module
8
- vi.mock("../../../lib/path.js", () => ({
9
- abbreviateHome: vi.fn((path: string) => path),
10
- }));
11
-
12
- describe.concurrent("BashRunner", () => {
13
- beforeEach(() => {
14
- vi.clearAllMocks();
15
- // Default mock implementation
16
- vi.mocked(pathModule.abbreviateHome).mockImplementation((p: string) => p);
17
- });
18
-
19
- afterEach(() => {
20
- cleanup();
21
- });
22
-
23
- it("should render command and output", () => {
24
- const instance = render(<BashRunner command="echo hello" output="hello" />);
25
-
26
- logInk(instance);
27
-
28
- const output = instance.frames.join("");
29
- expect(output).toContain("echo hello");
30
- expect(output).toContain("hello");
31
- });
32
-
33
- it("should render empty output", () => {
34
- const instance = render(<BashRunner command="ls" output="" />);
35
-
36
- logInk(instance);
37
-
38
- const output = instance.frames.join("");
39
- expect(output).toContain("ls");
40
- // Should not crash with empty output
41
- expect(output).toBeDefined();
42
- });
43
-
44
- it("should render multi-line output", () => {
45
- const multiLineOutput = "line1\nline2\nline3";
46
- const instance = render(<BashRunner command="cat file.txt" output={multiLineOutput} />);
47
-
48
- logInk(instance);
49
-
50
- const output = instance.frames.join("");
51
- expect(output).toContain("cat file.txt");
52
- expect(output).toContain("line1");
53
- expect(output).toContain("line2");
54
- expect(output).toContain("line3");
55
- });
56
-
57
- it("should not truncate when truncate is false", () => {
58
- const longOutput = Array(20)
59
- .fill("line")
60
- .map((_, i) => `line ${i}`)
61
- .join("\n");
62
- const instance = render(
63
- <BashRunner command="long-command" output={longOutput} truncate={false} />,
64
- );
65
-
66
- logInk(instance);
67
-
68
- const output = instance.frames.join("");
69
- // Should contain all lines when truncate is false
70
- expect(output).toContain("line 0");
71
- expect(output).toContain("line 19");
72
- expect(output).not.toContain("lines hidden");
73
- });
74
-
75
- it("should truncate output when truncate is true and output exceeds maxLines", () => {
76
- const longOutput = Array(20)
77
- .fill("line")
78
- .map((_, i) => `line ${i}`)
79
- .join("\n");
80
- const instance = render(
81
- <BashRunner command="long-command" output={longOutput} truncate={true} maxLines={10} />,
82
- );
83
-
84
- logInk(instance);
85
-
86
- const output = instance.frames.join("");
87
- // Should show first and last half of maxLines
88
- expect(output).toContain("line 0");
89
- expect(output).toContain("line 4"); // First half (5 lines)
90
- expect(output).toContain("line 15"); // Last half (5 lines)
91
- expect(output).toContain("line 19");
92
- expect(output).toContain("lines hidden");
93
- });
94
-
95
- it("should not truncate when output is within maxLines", () => {
96
- const shortOutput = Array(5)
97
- .fill("line")
98
- .map((_, i) => `line ${i}`)
99
- .join("\n");
100
- const instance = render(
101
- <BashRunner command="short-command" output={shortOutput} truncate={true} maxLines={10} />,
102
- );
103
-
104
- logInk(instance);
105
-
106
- const output = instance.frames.join("");
107
- expect(output).toContain("line 0");
108
- expect(output).toContain("line 4");
109
- expect(output).not.toContain("lines hidden");
110
- });
111
-
112
- it("should use default maxLines of 10 when not provided", () => {
113
- const longOutput = Array(20)
114
- .fill("line")
115
- .map((_, i) => `line ${i}`)
116
- .join("\n");
117
- const instance = render(<BashRunner command="test" output={longOutput} truncate={true} />);
118
-
119
- logInk(instance);
120
-
121
- const output = instance.frames.join("");
122
- // Should truncate at default 10 lines (5 + 5)
123
- expect(output).toContain("lines hidden");
124
- });
125
-
126
- it("should handle truncation with odd maxLines", () => {
127
- const longOutput = Array(20)
128
- .fill("line")
129
- .map((_, i) => `line ${i}`)
130
- .join("\n");
131
- const instance = render(
132
- <BashRunner command="test" output={longOutput} truncate={true} maxLines={9} />,
133
- );
134
-
135
- logInk(instance);
136
-
137
- const output = instance.frames.join("");
138
- // With maxLines=9, halfLines=4, so should show first 4 and last 4
139
- expect(output).toContain("line 0");
140
- expect(output).toContain("line 3");
141
- expect(output).toContain("line 16");
142
- expect(output).toContain("line 19");
143
- expect(output).toContain("lines hidden");
144
- });
145
-
146
- it("should display truncation indicator in the middle", () => {
147
- const longOutput = Array(20)
148
- .fill("line")
149
- .map((_, i) => `line ${i}`)
150
- .join("\n");
151
- const instance = render(
152
- <BashRunner command="test" output={longOutput} truncate={true} maxLines={10} />,
153
- );
154
-
155
- logInk(instance);
156
-
157
- const output = instance.frames.join("");
158
- // The truncation indicator should appear between first and last half
159
- const lines = output.split("\n");
160
- const truncationIndex = lines.findIndex((line) => line.includes("lines hidden"));
161
- expect(truncationIndex).toBeGreaterThan(-1);
162
- // Should be after first half and before last half
163
- expect(truncationIndex).toBeGreaterThan(0);
164
- expect(truncationIndex).toBeLessThan(lines.length - 1);
165
- });
166
-
167
- it("should call abbreviateHome with process.cwd()", () => {
168
- const instance = render(<BashRunner command="pwd" output="output" />);
169
-
170
- logInk(instance);
171
-
172
- expect(pathModule.abbreviateHome).toHaveBeenCalledWith(process.cwd());
173
- });
174
-
175
- it("should handle command with special characters", () => {
176
- const instance = render(<BashRunner command="echo 'hello world' && ls -la" output="output" />);
177
-
178
- logInk(instance);
179
-
180
- const output = instance.frames.join("");
181
- expect(output).toContain("echo 'hello world' && ls -la");
182
- });
183
-
184
- it("should handle output with special characters", () => {
185
- const specialOutput = "line with \"quotes\" and 'apostrophes' and $variables";
186
- const instance = render(<BashRunner command="test" output={specialOutput} />);
187
-
188
- logInk(instance);
189
-
190
- const output = instance.frames.join("");
191
- expect(output).toContain("quotes");
192
- expect(output).toContain("apostrophes");
193
- expect(output).toContain("$variables");
194
- });
195
-
196
- it("should handle very long single line output", () => {
197
- const veryLongLine = "x".repeat(1000);
198
- const instance = render(<BashRunner command="test" output={veryLongLine} />);
199
-
200
- logInk(instance);
201
-
202
- const output = instance.frames.join("");
203
- expect(output).toContain("x");
204
- expect(output).toBeDefined();
205
- });
206
-
207
- it("should handle edge case with maxLines=1", () => {
208
- const longOutput = Array(10)
209
- .fill("line")
210
- .map((_, i) => `line ${i}`)
211
- .join("\n");
212
- const instance = render(
213
- <BashRunner command="test" output={longOutput} truncate={true} maxLines={1} />,
214
- );
215
-
216
- logInk(instance);
217
-
218
- const output = instance.frames.join("");
219
- // Should show at least one line (the last line) even with maxLines=1
220
- expect(output).toBeDefined();
221
- expect(output).toContain("line 9"); // Last line should be shown
222
- expect(output).toContain("lines hidden");
223
- });
224
-
225
- it("should handle large number of lines", () => {
226
- const largeOutput = Array(1000)
227
- .fill("line")
228
- .map((_, i) => `line ${i}`)
229
- .join("\n");
230
- const instance = render(
231
- <BashRunner command="test" output={largeOutput} truncate={true} maxLines={20} />,
232
- );
233
-
234
- logInk(instance);
235
-
236
- const output = instance.frames.join("");
237
- expect(output).toContain("lines hidden");
238
- expect(output).toBeDefined();
239
- // Should show first and last portions of output
240
- expect(output).toContain("line 0");
241
- expect(output).toContain("line 999");
242
- });
243
-
244
- it("should not show truncation when lines exactly equal maxLines", () => {
245
- const exactOutput = Array(10)
246
- .fill("line")
247
- .map((_, i) => `line ${i}`)
248
- .join("\n");
249
- const instance = render(
250
- <BashRunner command="test" output={exactOutput} truncate={true} maxLines={10} />,
251
- );
252
-
253
- logInk(instance);
254
-
255
- const output = instance.frames.join("");
256
- // Should not show truncation indicator when lines.length === maxLines
257
- expect(output).toBeDefined();
258
- expect(output).not.toContain("lines hidden");
259
- // Should show all lines
260
- expect(output).toContain("line 0");
261
- expect(output).toContain("line 9");
262
- });
263
- });