wave-code 0.4.0 → 0.5.0

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 (50) hide show
  1. package/dist/commands/plugin/uninstall.js +1 -1
  2. package/dist/components/CommandSelector.js +3 -3
  3. package/dist/components/Confirmation.d.ts.map +1 -1
  4. package/dist/components/Confirmation.js +72 -19
  5. package/dist/components/DiffDisplay.d.ts +0 -1
  6. package/dist/components/DiffDisplay.d.ts.map +1 -1
  7. package/dist/components/DiffDisplay.js +38 -59
  8. package/dist/components/InputBox.d.ts.map +1 -1
  9. package/dist/components/InputBox.js +6 -5
  10. package/dist/components/Markdown.d.ts.map +1 -1
  11. package/dist/components/Markdown.js +2 -1
  12. package/dist/components/PlanDisplay.d.ts.map +1 -1
  13. package/dist/components/PlanDisplay.js +2 -2
  14. package/dist/components/PluginDetail.js +1 -1
  15. package/dist/components/SubagentBlock.d.ts.map +1 -1
  16. package/dist/components/SubagentBlock.js +4 -0
  17. package/dist/components/TaskManager.d.ts +6 -0
  18. package/dist/components/TaskManager.d.ts.map +1 -0
  19. package/dist/components/TaskManager.js +114 -0
  20. package/dist/components/ToolResultDisplay.d.ts.map +1 -1
  21. package/dist/components/ToolResultDisplay.js +2 -1
  22. package/dist/contexts/useChat.d.ts +5 -4
  23. package/dist/contexts/useChat.d.ts.map +1 -1
  24. package/dist/contexts/useChat.js +16 -12
  25. package/dist/hooks/useInputManager.d.ts +2 -2
  26. package/dist/hooks/useInputManager.js +10 -10
  27. package/dist/hooks/usePluginManager.d.ts.map +1 -1
  28. package/dist/hooks/usePluginManager.js +8 -4
  29. package/dist/managers/InputManager.d.ts +7 -6
  30. package/dist/managers/InputManager.d.ts.map +1 -1
  31. package/dist/managers/InputManager.js +17 -12
  32. package/package.json +6 -6
  33. package/src/commands/plugin/uninstall.ts +1 -1
  34. package/src/components/CommandSelector.tsx +3 -3
  35. package/src/components/Confirmation.tsx +114 -24
  36. package/src/components/DiffDisplay.tsx +60 -106
  37. package/src/components/InputBox.tsx +9 -7
  38. package/src/components/Markdown.tsx +7 -2
  39. package/src/components/PlanDisplay.tsx +14 -19
  40. package/src/components/PluginDetail.tsx +1 -1
  41. package/src/components/SubagentBlock.tsx +5 -0
  42. package/src/components/{BashShellManager.tsx → TaskManager.tsx} +79 -75
  43. package/src/components/ToolResultDisplay.tsx +4 -0
  44. package/src/contexts/useChat.tsx +25 -20
  45. package/src/hooks/useInputManager.ts +10 -10
  46. package/src/hooks/usePluginManager.ts +10 -4
  47. package/src/managers/InputManager.ts +22 -15
  48. package/dist/components/BashShellManager.d.ts +0 -6
  49. package/dist/components/BashShellManager.d.ts.map +0 -1
  50. package/dist/components/BashShellManager.js +0 -116
@@ -65,6 +65,7 @@ export interface ConfirmationProps {
65
65
  interface ConfirmationState {
66
66
  selectedOption: "allow" | "auto" | "alternative";
67
67
  alternativeText: string;
68
+ alternativeCursorPosition: number;
68
69
  hasUserInput: boolean; // to hide placeholder
69
70
  }
70
71
 
@@ -81,6 +82,7 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
81
82
  const [state, setState] = useState<ConfirmationState>({
82
83
  selectedOption: "allow",
83
84
  alternativeText: "",
85
+ alternativeCursorPosition: 0,
84
86
  hasUserInput: false,
85
87
  });
86
88
 
@@ -92,6 +94,7 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
92
94
  >(new Set());
93
95
  const [userAnswers, setUserAnswers] = useState<Record<string, string>>({});
94
96
  const [otherText, setOtherText] = useState("");
97
+ const [otherCursorPosition, setOtherCursorPosition] = useState(0);
95
98
 
96
99
  const questions =
97
100
  (toolInput as unknown as AskUserQuestionInput)?.questions || [];
@@ -159,6 +162,7 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
159
162
  setSelectedOptionIndex(0);
160
163
  setSelectedOptionIndices(new Set());
161
164
  setOtherText("");
165
+ setOtherCursorPosition(0);
162
166
  } else {
163
167
  // All questions answered
164
168
  onDecision({
@@ -207,10 +211,38 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
207
211
  }
208
212
 
209
213
  if (isOtherFocused) {
214
+ if (key.leftArrow) {
215
+ setOtherCursorPosition((prev) => Math.max(0, prev - 1));
216
+ return;
217
+ }
218
+ if (key.rightArrow) {
219
+ setOtherCursorPosition((prev) =>
220
+ Math.min(otherText.length, prev + 1),
221
+ );
222
+ return;
223
+ }
210
224
  if (key.backspace || key.delete) {
211
- setOtherText((prev) => prev.slice(0, -1));
212
- } else if (input && !key.ctrl && !key.meta) {
213
- setOtherText((prev) => prev + input);
225
+ if (otherCursorPosition > 0) {
226
+ setOtherText((prev) => {
227
+ const next =
228
+ prev.slice(0, otherCursorPosition - 1) +
229
+ prev.slice(otherCursorPosition);
230
+ return next;
231
+ });
232
+ setOtherCursorPosition((prev) => prev - 1);
233
+ }
234
+ return;
235
+ }
236
+ if (input && !key.ctrl && !key.meta) {
237
+ setOtherText((prev) => {
238
+ const next =
239
+ prev.slice(0, otherCursorPosition) +
240
+ input +
241
+ prev.slice(otherCursorPosition);
242
+ return next;
243
+ });
244
+ setOtherCursorPosition((prev) => prev + input.length);
245
+ return;
214
246
  }
215
247
  return;
216
248
  }
@@ -253,6 +285,29 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
253
285
  return;
254
286
  }
255
287
 
288
+ if (state.selectedOption === "alternative") {
289
+ if (key.leftArrow) {
290
+ setState((prev) => ({
291
+ ...prev,
292
+ alternativeCursorPosition: Math.max(
293
+ 0,
294
+ prev.alternativeCursorPosition - 1,
295
+ ),
296
+ }));
297
+ return;
298
+ }
299
+ if (key.rightArrow) {
300
+ setState((prev) => ({
301
+ ...prev,
302
+ alternativeCursorPosition: Math.min(
303
+ prev.alternativeText.length,
304
+ prev.alternativeCursorPosition + 1,
305
+ ),
306
+ }));
307
+ return;
308
+ }
309
+ }
310
+
256
311
  // Handle arrow keys for navigation
257
312
  if (key.upArrow) {
258
313
  setState((prev) => {
@@ -287,27 +342,42 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
287
342
  // Handle text input for alternative option
288
343
  if (input && !key.ctrl && !key.meta && !("alt" in key && key.alt)) {
289
344
  // Focus on alternative option when user starts typing
290
- setState((prev) => ({
291
- selectedOption: "alternative",
292
- alternativeText: prev.alternativeText + input,
293
- hasUserInput: true,
294
- }));
295
- return;
296
- }
297
-
298
- // Handle backspace and delete (same behavior - delete one character)
299
- if (key.backspace || key.delete) {
300
345
  setState((prev) => {
301
- const newText = prev.alternativeText.slice(0, -1);
346
+ const nextText =
347
+ prev.alternativeText.slice(0, prev.alternativeCursorPosition) +
348
+ input +
349
+ prev.alternativeText.slice(prev.alternativeCursorPosition);
302
350
  return {
303
351
  ...prev,
304
352
  selectedOption: "alternative",
305
- alternativeText: newText,
306
- hasUserInput: newText.length > 0,
353
+ alternativeText: nextText,
354
+ alternativeCursorPosition:
355
+ prev.alternativeCursorPosition + input.length,
356
+ hasUserInput: true,
307
357
  };
308
358
  });
309
359
  return;
310
360
  }
361
+
362
+ // Handle backspace and delete
363
+ if (key.backspace || key.delete) {
364
+ setState((prev) => {
365
+ if (prev.alternativeCursorPosition > 0) {
366
+ const nextText =
367
+ prev.alternativeText.slice(0, prev.alternativeCursorPosition - 1) +
368
+ prev.alternativeText.slice(prev.alternativeCursorPosition);
369
+ return {
370
+ ...prev,
371
+ selectedOption: "alternative",
372
+ alternativeText: nextText,
373
+ alternativeCursorPosition: prev.alternativeCursorPosition - 1,
374
+ hasUserInput: nextText.length > 0,
375
+ };
376
+ }
377
+ return prev;
378
+ });
379
+ return;
380
+ }
311
381
  });
312
382
 
313
383
  const placeholderText = "Type here to tell Wave what to do differently";
@@ -329,11 +399,7 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
329
399
  </Text>
330
400
  <Text color="yellow">{getActionDescription(toolName, toolInput)}</Text>
331
401
 
332
- <DiffDisplay
333
- toolName={toolName}
334
- parameters={JSON.stringify(toolInput)}
335
- isExpanded={isExpanded}
336
- />
402
+ <DiffDisplay toolName={toolName} parameters={JSON.stringify(toolInput)} />
337
403
 
338
404
  {toolName === ASK_USER_QUESTION_TOOL_NAME &&
339
405
  currentQuestion &&
@@ -383,7 +449,15 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
383
449
  {isOther && isSelected && (
384
450
  <Text>
385
451
  :{" "}
386
- {otherText || (
452
+ {otherText ? (
453
+ <>
454
+ {otherText.slice(0, otherCursorPosition)}
455
+ <Text backgroundColor="white" color="black">
456
+ {otherText[otherCursorPosition] || " "}
457
+ </Text>
458
+ {otherText.slice(otherCursorPosition + 1)}
459
+ </>
460
+ ) : (
387
461
  <Text color="gray" dimColor>
388
462
  [Type your answer...]
389
463
  </Text>
@@ -474,8 +548,24 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
474
548
  </Text>
475
549
  ) : (
476
550
  <Text>
477
- {state.alternativeText ||
478
- "Type here to tell Wave what to do differently"}
551
+ {state.alternativeText ? (
552
+ <>
553
+ {state.alternativeText.slice(
554
+ 0,
555
+ state.alternativeCursorPosition,
556
+ )}
557
+ <Text backgroundColor="white" color="black">
558
+ {state.alternativeText[
559
+ state.alternativeCursorPosition
560
+ ] || " "}
561
+ </Text>
562
+ {state.alternativeText.slice(
563
+ state.alternativeCursorPosition + 1,
564
+ )}
565
+ </>
566
+ ) : (
567
+ "Type here to tell Wave what to do differently"
568
+ )}
479
569
  </Text>
480
570
  )}
481
571
  </Text>
@@ -1,5 +1,5 @@
1
1
  import React, { useMemo } from "react";
2
- import { Box, Text, useStdout } from "ink";
2
+ import { Box, Text } from "ink";
3
3
  import {
4
4
  WRITE_TOOL_NAME,
5
5
  EDIT_TOOL_NAME,
@@ -11,19 +11,12 @@ import { diffLines, diffWords } from "diff";
11
11
  interface DiffDisplayProps {
12
12
  toolName?: string;
13
13
  parameters?: string;
14
- isExpanded?: boolean;
15
14
  }
16
15
 
17
16
  export const DiffDisplay: React.FC<DiffDisplayProps> = ({
18
17
  toolName,
19
18
  parameters,
20
- isExpanded = false,
21
19
  }) => {
22
- const { stdout } = useStdout();
23
- const maxHeight = useMemo(() => {
24
- return Math.max(5, (stdout?.rows || 24) - 20);
25
- }, [stdout?.rows]);
26
-
27
20
  const showDiff =
28
21
  toolName &&
29
22
  [WRITE_TOOL_NAME, EDIT_TOOL_NAME, MULTI_EDIT_TOOL_NAME].includes(toolName);
@@ -107,7 +100,7 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
107
100
  }
108
101
  };
109
102
 
110
- // Render expanded diff display using word-level diff for all changes
103
+ // Render expanded diff display
111
104
  const renderExpandedDiff = () => {
112
105
  try {
113
106
  if (changes.length === 0) return null;
@@ -122,9 +115,8 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
122
115
  change.newContent || "",
123
116
  );
124
117
 
118
+ // Process line diffs
125
119
  const diffElements: React.ReactNode[] = [];
126
-
127
- // Process line diffs and apply word-level diff to changed lines
128
120
  lineDiffs.forEach((part, partIndex) => {
129
121
  if (part.added) {
130
122
  const lines = part.value
@@ -175,70 +167,46 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
175
167
  }
176
168
  });
177
169
 
178
- // Now look for pairs of removed/added lines that can be word-diffed
179
- let i = 0;
180
-
181
- while (i < diffElements.length) {
182
- const current = diffElements[i];
183
- const next =
184
- i + 1 < diffElements.length ? diffElements[i + 1] : null;
185
-
186
- // Check if we have a removed line followed by an added line
187
- const currentKey = React.isValidElement(current) ? current.key : "";
188
- const nextKey = React.isValidElement(next) ? next.key : "";
189
-
190
- const isCurrentRemoved =
191
- typeof currentKey === "string" && currentKey.includes("remove-");
192
- const isNextAdded =
193
- typeof nextKey === "string" && nextKey.includes("add-");
170
+ // If it's a single line change (one removed, one added), use word-level diff
171
+ if (
172
+ diffElements.length === 2 &&
173
+ React.isValidElement(diffElements[0]) &&
174
+ React.isValidElement(diffElements[1]) &&
175
+ typeof diffElements[0].key === "string" &&
176
+ diffElements[0].key.includes("remove-") &&
177
+ typeof diffElements[1].key === "string" &&
178
+ diffElements[1].key.includes("add-")
179
+ ) {
180
+ const removedText = extractTextFromElement(diffElements[0]);
181
+ const addedText = extractTextFromElement(diffElements[1]);
194
182
 
195
- if (
196
- isCurrentRemoved &&
197
- isNextAdded &&
198
- React.isValidElement(current) &&
199
- React.isValidElement(next)
200
- ) {
201
- // Extract the text content from the removed and added lines
202
- const removedText = extractTextFromElement(current);
203
- const addedText = extractTextFromElement(next);
204
-
205
- if (removedText && addedText) {
206
- // Apply word-level diff
207
- const { removedParts, addedParts } = renderWordLevelDiff(
208
- removedText,
209
- addedText,
210
- `word-${changeIndex}-${i}`,
211
- );
212
-
213
- allElements.push(
214
- <Box
215
- key={`word-diff-removed-${changeIndex}-${i}`}
216
- flexDirection="row"
217
- >
218
- <Text color="red">-</Text>
219
- {removedParts}
220
- </Box>,
221
- );
222
- allElements.push(
223
- <Box
224
- key={`word-diff-added-${changeIndex}-${i}`}
225
- flexDirection="row"
226
- >
227
- <Text color="green">+</Text>
228
- {addedParts}
229
- </Box>,
230
- );
183
+ if (removedText && addedText) {
184
+ const { removedParts, addedParts } = renderWordLevelDiff(
185
+ removedText,
186
+ addedText,
187
+ `word-${changeIndex}`,
188
+ );
231
189
 
232
- i += 2; // Skip the next element since we processed it
233
- } else {
234
- // Fallback to original elements
235
- allElements.push(current);
236
- i += 1;
237
- }
190
+ allElements.push(
191
+ <Box
192
+ key={`word-diff-removed-${changeIndex}`}
193
+ flexDirection="row"
194
+ >
195
+ <Text color="red">-</Text>
196
+ {removedParts}
197
+ </Box>,
198
+ );
199
+ allElements.push(
200
+ <Box key={`word-diff-added-${changeIndex}`} flexDirection="row">
201
+ <Text color="green">+</Text>
202
+ {addedParts}
203
+ </Box>,
204
+ );
238
205
  } else {
239
- allElements.push(current);
240
- i += 1;
206
+ allElements.push(...diffElements);
241
207
  }
208
+ } else {
209
+ allElements.push(...diffElements);
242
210
  }
243
211
  } catch (error) {
244
212
  console.warn(
@@ -255,21 +223,7 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
255
223
  }
256
224
  });
257
225
 
258
- const isTruncated = !isExpanded && allElements.length > maxHeight;
259
- const displayElements = isTruncated
260
- ? allElements.slice(0, maxHeight - 1)
261
- : allElements;
262
-
263
- return (
264
- <Box flexDirection="column">
265
- {displayElements}
266
- {isTruncated && (
267
- <Text color="yellow" dimColor>
268
- ... (truncated {allElements.length - (maxHeight - 1)} more lines)
269
- </Text>
270
- )}
271
- </Box>
272
- );
226
+ return <Box flexDirection="column">{allElements}</Box>;
273
227
  } catch (error) {
274
228
  console.warn("Error rendering expanded diff:", error);
275
229
  return (
@@ -280,26 +234,6 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
280
234
  }
281
235
  };
282
236
 
283
- // Helper function to extract text content from a React element
284
- const extractTextFromElement = (element: React.ReactNode): string | null => {
285
- if (!React.isValidElement(element)) return null;
286
-
287
- // Navigate through Box -> Text structure
288
- const children = (
289
- element.props as unknown as { children?: React.ReactNode[] }
290
- ).children;
291
- if (Array.isArray(children) && children.length >= 2) {
292
- const textElement = children[1]; // Second child should be the Text with content
293
- if (
294
- React.isValidElement(textElement) &&
295
- (textElement.props as unknown as { children?: string }).children
296
- ) {
297
- return (textElement.props as unknown as { children: string }).children;
298
- }
299
- }
300
- return null;
301
- };
302
-
303
237
  // Don't render anything if no diff should be shown
304
238
  if (!showDiff) {
305
239
  return null;
@@ -316,3 +250,23 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
316
250
  </Box>
317
251
  );
318
252
  };
253
+
254
+ // Helper function to extract text content from a React element
255
+ const extractTextFromElement = (element: React.ReactNode): string | null => {
256
+ if (!React.isValidElement(element)) return null;
257
+
258
+ // Navigate through Box -> Text structure
259
+ const children = (
260
+ element.props as unknown as { children?: React.ReactNode[] }
261
+ ).children;
262
+ if (Array.isArray(children) && children.length >= 2) {
263
+ const textElement = children[1]; // Second child should be the Text with content
264
+ if (
265
+ React.isValidElement(textElement) &&
266
+ (textElement.props as unknown as { children?: string }).children
267
+ ) {
268
+ return (textElement.props as unknown as { children: string }).children;
269
+ }
270
+ }
271
+ return null;
272
+ };
@@ -5,7 +5,7 @@ import { FileSelector } from "./FileSelector.js";
5
5
  import { CommandSelector } from "./CommandSelector.js";
6
6
  import { HistorySearch } from "./HistorySearch.js";
7
7
  import { MemoryTypeSelector } from "./MemoryTypeSelector.js";
8
- import { BashShellManager } from "./BashShellManager.js";
8
+ import { TaskManager } from "./TaskManager.js";
9
9
  import { McpManager } from "./McpManager.js";
10
10
  import { RewindCommand } from "./RewindCommand.js";
11
11
  import { useInputManager } from "../hooks/useInputManager.js";
@@ -58,6 +58,7 @@ export const InputBox: React.FC<InputBoxProps> = ({
58
58
  permissionMode: chatPermissionMode,
59
59
  setPermissionMode: setChatPermissionMode,
60
60
  handleRewindSelect,
61
+ backgroundCurrentTask,
61
62
  messages,
62
63
  } = useChat();
63
64
 
@@ -90,11 +91,11 @@ export const InputBox: React.FC<InputBoxProps> = ({
90
91
  // History search
91
92
  showHistorySearch,
92
93
  historySearchQuery,
93
- // Bash/MCP Manager
94
- showBashManager,
94
+ // Task/MCP Manager
95
+ showTaskManager,
95
96
  showMcpManager,
96
97
  showRewindManager,
97
- setShowBashManager,
98
+ setShowTaskManager,
98
99
  setShowMcpManager,
99
100
  setShowRewindManager,
100
101
  // Permission mode
@@ -111,6 +112,7 @@ export const InputBox: React.FC<InputBoxProps> = ({
111
112
  onHasSlashCommand: hasSlashCommand,
112
113
  onSaveMemory: saveMemory,
113
114
  onAbortMessage: abortMessage,
115
+ onBackgroundCurrentTask: backgroundCurrentTask,
114
116
  onPermissionModeChange: setChatPermissionMode,
115
117
  });
116
118
 
@@ -216,8 +218,8 @@ export const InputBox: React.FC<InputBoxProps> = ({
216
218
  />
217
219
  )}
218
220
 
219
- {showBashManager && (
220
- <BashShellManager onCancel={() => setShowBashManager(false)} />
221
+ {showTaskManager && (
222
+ <TaskManager onCancel={() => setShowTaskManager(false)} />
221
223
  )}
222
224
 
223
225
  {showMcpManager && (
@@ -229,7 +231,7 @@ export const InputBox: React.FC<InputBoxProps> = ({
229
231
  />
230
232
  )}
231
233
 
232
- {showBashManager || showMcpManager || showRewindManager || (
234
+ {showTaskManager || showMcpManager || showRewindManager || (
233
235
  <Box flexDirection="column">
234
236
  <Box
235
237
  borderStyle="single"
@@ -271,8 +271,13 @@ const BlockRenderer = ({ tokens }: { tokens: Token[] }) => {
271
271
  </Text>
272
272
  <Box flexDirection="column" flexGrow={1}>
273
273
  {item.tokens.map((itemToken, itemIndex) => {
274
- if (itemToken.type === "text") {
275
- const it = itemToken as Tokens.Text;
274
+ if (
275
+ itemToken.type === "text" ||
276
+ itemToken.type === "paragraph"
277
+ ) {
278
+ const it = itemToken as
279
+ | Tokens.Text
280
+ | Tokens.Paragraph;
276
281
  return (
277
282
  <Box key={itemIndex} flexDirection="row">
278
283
  <Text>
@@ -14,7 +14,7 @@ export const PlanDisplay: React.FC<PlanDisplayProps> = ({
14
14
  const { stdout } = useStdout();
15
15
  const maxHeight = useMemo(() => {
16
16
  // Similar to DiffDisplay.tsx maxHeight calculation
17
- return Math.max(5, (stdout?.rows || 24) - 20);
17
+ return Math.max(5, (stdout?.rows || 24) - 25);
18
18
  }, [stdout?.rows]);
19
19
 
20
20
  const lines = useMemo(() => plan.split("\n"), [plan]);
@@ -22,25 +22,20 @@ export const PlanDisplay: React.FC<PlanDisplayProps> = ({
22
22
 
23
23
  return (
24
24
  <Box flexDirection="column" marginTop={1}>
25
- <Box paddingLeft={2} borderLeft borderColor="cyan" flexDirection="column">
26
- <Text color="cyan" bold>
27
- Plan Content:
28
- </Text>
29
- <Box
30
- flexDirection="column"
31
- height={isOverflowing ? maxHeight : undefined}
32
- overflow="hidden"
33
- >
34
- <Markdown>{plan}</Markdown>
35
- </Box>
36
- {isOverflowing && (
37
- <Box marginTop={1}>
38
- <Text color="yellow" dimColor>
39
- ... (plan truncated, {lines.length} lines total)
40
- </Text>
41
- </Box>
42
- )}
25
+ <Box
26
+ flexDirection="column"
27
+ height={isOverflowing ? maxHeight : undefined}
28
+ overflow="hidden"
29
+ >
30
+ <Markdown>{plan}</Markdown>
43
31
  </Box>
32
+ {isOverflowing && (
33
+ <Box marginTop={1}>
34
+ <Text color="yellow" dimColor>
35
+ ... (plan truncated, {lines.length} lines total)
36
+ </Text>
37
+ </Box>
38
+ )}
44
39
  </Box>
45
40
  );
46
41
  };
@@ -23,8 +23,8 @@ export const PluginDetail: React.FC = () => {
23
23
  );
24
24
 
25
25
  const INSTALLED_ACTIONS = [
26
- { id: "uninstall", label: "Uninstall plugin" },
27
26
  { id: "update", label: "Update plugin (reinstall)" },
27
+ { id: "uninstall", label: "Uninstall plugin" },
28
28
  ] as const;
29
29
 
30
30
  const isInstalledAndEnabled = plugin && "enabled" in plugin && plugin.enabled;
@@ -11,6 +11,11 @@ interface SubagentBlockProps {
11
11
  export const SubagentBlock: React.FC<SubagentBlockProps> = ({ block }) => {
12
12
  const { subagentMessages } = useChat();
13
13
 
14
+ // If the subagent is running in the background, don't show the block
15
+ if (block.runInBackground) {
16
+ return null;
17
+ }
18
+
14
19
  // Get messages for this subagent from context
15
20
  const messages = subagentMessages[block.subagentId] || [];
16
21