@abacus-ai/cli 2.0.0-canary.1 → 2.0.0-canary.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 (197) hide show
  1. package/dist/index.mjs +450 -422
  2. package/package.json +4 -1
  3. package/.oxlintrc.json +0 -8
  4. package/resources/abacus.ico +0 -0
  5. package/resources/entitlements.plist +0 -9
  6. package/src/__e2e__/README.md +0 -196
  7. package/src/__e2e__/agent-interactions.e2e.test.tsx +0 -61
  8. package/src/__e2e__/cli-commands.e2e.test.tsx +0 -77
  9. package/src/__e2e__/conversation-throttle.e2e.test.ts +0 -453
  10. package/src/__e2e__/conversation.e2e.test.tsx +0 -56
  11. package/src/__e2e__/diff-preview.e2e.test.tsx +0 -3399
  12. package/src/__e2e__/file-creation.e2e.test.tsx +0 -149
  13. package/src/__e2e__/helpers/test-helpers.ts +0 -449
  14. package/src/__e2e__/keyboard-navigation.e2e.test.tsx +0 -34
  15. package/src/__e2e__/llm-models.e2e.test.ts +0 -402
  16. package/src/__e2e__/mcp/mcp-callback-flow.e2e.test.tsx +0 -71
  17. package/src/__e2e__/mcp/mcp-full-app-ui.e2e.test.tsx +0 -167
  18. package/src/__e2e__/mcp/mcp-ui-rendering.e2e.test.tsx +0 -185
  19. package/src/__e2e__/repl.e2e.test.tsx +0 -78
  20. package/src/__e2e__/shell-compatibility.e2e.test.tsx +0 -76
  21. package/src/__e2e__/theme-mcp.e2e.test.tsx +0 -98
  22. package/src/__e2e__/tool-permissions.e2e.test.tsx +0 -66
  23. package/src/args.ts +0 -22
  24. package/src/components/__tests__/react-compiler.test.tsx +0 -78
  25. package/src/components/__tests__/status-indicator.test.tsx +0 -403
  26. package/src/components/composer/__tests__/bash-runner.test.tsx +0 -263
  27. package/src/components/composer/agent-mode-indicator.tsx +0 -63
  28. package/src/components/composer/bash-runner.tsx +0 -54
  29. package/src/components/composer/commands/default-commands.tsx +0 -615
  30. package/src/components/composer/commands/handler.tsx +0 -59
  31. package/src/components/composer/commands/picker.tsx +0 -273
  32. package/src/components/composer/commands/registry.ts +0 -233
  33. package/src/components/composer/commands/types.ts +0 -33
  34. package/src/components/composer/context.tsx +0 -88
  35. package/src/components/composer/file-mention-picker.tsx +0 -83
  36. package/src/components/composer/help.tsx +0 -44
  37. package/src/components/composer/index.tsx +0 -1007
  38. package/src/components/composer/mentions.ts +0 -57
  39. package/src/components/composer/message-queue.tsx +0 -70
  40. package/src/components/composer/mode-panel.tsx +0 -35
  41. package/src/components/composer/modes/__tests__/bash-handler.test.tsx +0 -755
  42. package/src/components/composer/modes/__tests__/bash-renderer.test.tsx +0 -1108
  43. package/src/components/composer/modes/bash-handler.tsx +0 -132
  44. package/src/components/composer/modes/bash-renderer.tsx +0 -175
  45. package/src/components/composer/modes/default-handlers.tsx +0 -33
  46. package/src/components/composer/modes/index.ts +0 -41
  47. package/src/components/composer/modes/types.ts +0 -21
  48. package/src/components/composer/persistent-shell.ts +0 -283
  49. package/src/components/composer/process.ts +0 -65
  50. package/src/components/composer/types.ts +0 -9
  51. package/src/components/composer/use-mention-search.ts +0 -68
  52. package/src/components/error-boundry.tsx +0 -60
  53. package/src/components/exit-message.tsx +0 -29
  54. package/src/components/expanded-view.tsx +0 -74
  55. package/src/components/file-completion.tsx +0 -127
  56. package/src/components/header.tsx +0 -47
  57. package/src/components/logo.tsx +0 -37
  58. package/src/components/segments.tsx +0 -356
  59. package/src/components/status-indicator.tsx +0 -306
  60. package/src/components/tool-group-summary.tsx +0 -263
  61. package/src/components/tool-permissions/ask-user-question-permission-ui.tsx +0 -319
  62. package/src/components/tool-permissions/diff-preview.tsx +0 -359
  63. package/src/components/tool-permissions/index.ts +0 -5
  64. package/src/components/tool-permissions/permission-options.tsx +0 -401
  65. package/src/components/tool-permissions/permission-preview-header.tsx +0 -57
  66. package/src/components/tool-permissions/tool-permission-ui.tsx +0 -420
  67. package/src/components/tools/agent/ask-user-question.tsx +0 -107
  68. package/src/components/tools/agent/enter-plan-mode.tsx +0 -55
  69. package/src/components/tools/agent/exit-plan-mode.tsx +0 -83
  70. package/src/components/tools/agent/handoff-to-main.tsx +0 -27
  71. package/src/components/tools/agent/subagent.tsx +0 -37
  72. package/src/components/tools/agent/todo-write.tsx +0 -104
  73. package/src/components/tools/browser/close-tab.tsx +0 -58
  74. package/src/components/tools/browser/computer.tsx +0 -70
  75. package/src/components/tools/browser/get-interactive-elements.tsx +0 -54
  76. package/src/components/tools/browser/get-tab-content.tsx +0 -51
  77. package/src/components/tools/browser/navigate-to.tsx +0 -59
  78. package/src/components/tools/browser/new-tab.tsx +0 -60
  79. package/src/components/tools/browser/perform-action.tsx +0 -63
  80. package/src/components/tools/browser/refresh-tab.tsx +0 -43
  81. package/src/components/tools/browser/switch-tab.tsx +0 -58
  82. package/src/components/tools/filesystem/delete-file.tsx +0 -104
  83. package/src/components/tools/filesystem/edit.tsx +0 -220
  84. package/src/components/tools/filesystem/list-dir.tsx +0 -78
  85. package/src/components/tools/filesystem/read-file.tsx +0 -180
  86. package/src/components/tools/filesystem/upload-image.tsx +0 -76
  87. package/src/components/tools/ide/ide-diagnostics.tsx +0 -62
  88. package/src/components/tools/index.ts +0 -91
  89. package/src/components/tools/mcp/mcp-tool.tsx +0 -158
  90. package/src/components/tools/search/fetch-url.tsx +0 -73
  91. package/src/components/tools/search/file-search.tsx +0 -78
  92. package/src/components/tools/search/grep.tsx +0 -90
  93. package/src/components/tools/search/semantic-search.tsx +0 -66
  94. package/src/components/tools/search/web-search.tsx +0 -71
  95. package/src/components/tools/shared/index.tsx +0 -48
  96. package/src/components/tools/shared/zod-coercion.ts +0 -35
  97. package/src/components/tools/terminal/bash-tool-output.tsx +0 -188
  98. package/src/components/tools/terminal/get-terminal-output.tsx +0 -91
  99. package/src/components/tools/terminal/run-in-terminal.tsx +0 -131
  100. package/src/components/tools/types.ts +0 -16
  101. package/src/components/tools.tsx +0 -68
  102. package/src/components/ui/__tests__/divider.test.tsx +0 -61
  103. package/src/components/ui/__tests__/gradient.test.tsx +0 -125
  104. package/src/components/ui/__tests__/input.test.tsx +0 -166
  105. package/src/components/ui/__tests__/select.test.tsx +0 -273
  106. package/src/components/ui/__tests__/shimmer.test.tsx +0 -99
  107. package/src/components/ui/blinking-indicator.tsx +0 -27
  108. package/src/components/ui/divider.tsx +0 -162
  109. package/src/components/ui/gradient.tsx +0 -56
  110. package/src/components/ui/input.tsx +0 -228
  111. package/src/components/ui/select.tsx +0 -151
  112. package/src/components/ui/shimmer.tsx +0 -76
  113. package/src/context/agent-mode.tsx +0 -95
  114. package/src/context/extension-file.tsx +0 -136
  115. package/src/context/network-activity.tsx +0 -45
  116. package/src/context/notification.tsx +0 -62
  117. package/src/context/shell-size.tsx +0 -49
  118. package/src/context/shell-title.tsx +0 -38
  119. package/src/entrypoints/print-mode.ts +0 -312
  120. package/src/entrypoints/repl.tsx +0 -389
  121. package/src/hooks/use-agent.ts +0 -15
  122. package/src/hooks/use-api-client.ts +0 -1
  123. package/src/hooks/use-available-height.ts +0 -8
  124. package/src/hooks/use-cleanup.ts +0 -29
  125. package/src/hooks/use-interrupt-manager.ts +0 -242
  126. package/src/hooks/use-models.ts +0 -22
  127. package/src/index.ts +0 -217
  128. package/src/lib/__tests__/ansi.test.ts +0 -255
  129. package/src/lib/__tests__/cli.test.ts +0 -122
  130. package/src/lib/__tests__/commands.test.ts +0 -325
  131. package/src/lib/__tests__/constants.test.ts +0 -15
  132. package/src/lib/__tests__/focusables.test.ts +0 -25
  133. package/src/lib/__tests__/fs.test.ts +0 -231
  134. package/src/lib/__tests__/markdown.test.tsx +0 -348
  135. package/src/lib/__tests__/mcpCommandHandler.test.ts +0 -173
  136. package/src/lib/__tests__/mcpManagement.test.ts +0 -38
  137. package/src/lib/__tests__/path-paste.test.ts +0 -144
  138. package/src/lib/__tests__/path.test.ts +0 -300
  139. package/src/lib/__tests__/queries.test.ts +0 -39
  140. package/src/lib/__tests__/standaloneMcpService.test.ts +0 -71
  141. package/src/lib/__tests__/text-buffer.test.ts +0 -328
  142. package/src/lib/__tests__/text-utils.test.ts +0 -32
  143. package/src/lib/__tests__/timing.test.ts +0 -78
  144. package/src/lib/__tests__/utils.test.ts +0 -238
  145. package/src/lib/__tests__/vim-buffer-actions.test.ts +0 -154
  146. package/src/lib/ansi.ts +0 -150
  147. package/src/lib/cli-push-server.ts +0 -112
  148. package/src/lib/cli.ts +0 -44
  149. package/src/lib/clipboard.ts +0 -226
  150. package/src/lib/command-utils.ts +0 -93
  151. package/src/lib/commands.ts +0 -270
  152. package/src/lib/constants.ts +0 -3
  153. package/src/lib/extension-connection.ts +0 -181
  154. package/src/lib/focusables.ts +0 -7
  155. package/src/lib/fs.ts +0 -533
  156. package/src/lib/markdown/code-block.tsx +0 -63
  157. package/src/lib/markdown/index.ts +0 -4
  158. package/src/lib/markdown/link.tsx +0 -19
  159. package/src/lib/markdown/markdown.tsx +0 -372
  160. package/src/lib/markdown/types.ts +0 -15
  161. package/src/lib/mcpCommandHandler.ts +0 -121
  162. package/src/lib/mcpManagement.ts +0 -44
  163. package/src/lib/path-paste.ts +0 -185
  164. package/src/lib/path.ts +0 -179
  165. package/src/lib/queries.ts +0 -15
  166. package/src/lib/standaloneMcpService.ts +0 -688
  167. package/src/lib/status-utils.ts +0 -237
  168. package/src/lib/test-utils.tsx +0 -72
  169. package/src/lib/text-buffer.ts +0 -2415
  170. package/src/lib/text-utils.ts +0 -272
  171. package/src/lib/timing.ts +0 -63
  172. package/src/lib/types.ts +0 -295
  173. package/src/lib/utils.ts +0 -182
  174. package/src/lib/vim-buffer-actions.ts +0 -732
  175. package/src/providers/agent.tsx +0 -1063
  176. package/src/providers/api-client.tsx +0 -43
  177. package/src/services/logger.ts +0 -85
  178. package/src/terminal/detection.ts +0 -187
  179. package/src/terminal/exit.ts +0 -279
  180. package/src/terminal/notification.ts +0 -83
  181. package/src/terminal/progress.ts +0 -201
  182. package/src/terminal/setup.ts +0 -797
  183. package/src/terminal/types.ts +0 -51
  184. package/src/theme/context.tsx +0 -57
  185. package/src/theme/index.ts +0 -4
  186. package/src/theme/themed.tsx +0 -35
  187. package/src/theme/themes.json +0 -546
  188. package/src/theme/types.ts +0 -110
  189. package/src/tools/types.ts +0 -59
  190. package/src/tools/utils/__tests__/zod-coercion.test.ts +0 -33
  191. package/src/tools/utils/tool-ui-components.tsx +0 -649
  192. package/src/tools/utils/zod-coercion.ts +0 -35
  193. package/tsconfig.json +0 -16
  194. package/tsconfig.node.json +0 -29
  195. package/tsconfig.test.json +0 -27
  196. package/tsdown.config.ts +0 -17
  197. 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
- });