wave-code 0.4.0 → 0.6.1

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 (105) hide show
  1. package/dist/commands/plugin/uninstall.js +1 -1
  2. package/dist/components/App.d.ts.map +1 -1
  3. package/dist/components/App.js +38 -2
  4. package/dist/components/BackgroundTaskManager.d.ts +6 -0
  5. package/dist/components/BackgroundTaskManager.d.ts.map +1 -0
  6. package/dist/components/BackgroundTaskManager.js +114 -0
  7. package/dist/components/ChatInterface.d.ts.map +1 -1
  8. package/dist/components/ChatInterface.js +39 -5
  9. package/dist/components/CommandSelector.d.ts.map +1 -1
  10. package/dist/components/CommandSelector.js +13 -5
  11. package/dist/components/CompressDisplay.d.ts.map +1 -1
  12. package/dist/components/CompressDisplay.js +6 -10
  13. package/dist/components/ConfirmationDetails.d.ts +9 -0
  14. package/dist/components/ConfirmationDetails.d.ts.map +1 -0
  15. package/dist/components/ConfirmationDetails.js +53 -0
  16. package/dist/components/{Confirmation.d.ts → ConfirmationSelector.d.ts} +3 -3
  17. package/dist/components/ConfirmationSelector.d.ts.map +1 -0
  18. package/dist/components/{Confirmation.js → ConfirmationSelector.js} +92 -101
  19. package/dist/components/DiffDisplay.d.ts +0 -1
  20. package/dist/components/DiffDisplay.d.ts.map +1 -1
  21. package/dist/components/DiffDisplay.js +82 -60
  22. package/dist/components/FileSelector.d.ts.map +1 -1
  23. package/dist/components/FileSelector.js +2 -2
  24. package/dist/components/HistorySearch.d.ts.map +1 -1
  25. package/dist/components/HistorySearch.js +12 -4
  26. package/dist/components/InputBox.d.ts +1 -3
  27. package/dist/components/InputBox.d.ts.map +1 -1
  28. package/dist/components/InputBox.js +9 -18
  29. package/dist/components/LoadingIndicator.d.ts +11 -0
  30. package/dist/components/LoadingIndicator.d.ts.map +1 -0
  31. package/dist/components/LoadingIndicator.js +6 -0
  32. package/dist/components/Markdown.d.ts.map +1 -1
  33. package/dist/components/Markdown.js +114 -120
  34. package/dist/components/MessageItem.d.ts.map +1 -1
  35. package/dist/components/MessageItem.js +1 -2
  36. package/dist/components/MessageList.d.ts +2 -3
  37. package/dist/components/MessageList.d.ts.map +1 -1
  38. package/dist/components/MessageList.js +7 -7
  39. package/dist/components/PlanDisplay.d.ts.map +1 -1
  40. package/dist/components/PlanDisplay.js +4 -12
  41. package/dist/components/PluginDetail.js +1 -1
  42. package/dist/components/RewindCommand.d.ts +4 -0
  43. package/dist/components/RewindCommand.d.ts.map +1 -1
  44. package/dist/components/RewindCommand.js +19 -2
  45. package/dist/components/SubagentBlock.d.ts.map +1 -1
  46. package/dist/components/SubagentBlock.js +12 -5
  47. package/dist/components/TaskList.d.ts +3 -0
  48. package/dist/components/TaskList.d.ts.map +1 -0
  49. package/dist/components/TaskList.js +49 -0
  50. package/dist/components/ToolResultDisplay.d.ts.map +1 -1
  51. package/dist/components/ToolResultDisplay.js +2 -1
  52. package/dist/contexts/useChat.d.ts +15 -6
  53. package/dist/contexts/useChat.d.ts.map +1 -1
  54. package/dist/contexts/useChat.js +52 -43
  55. package/dist/hooks/useInputManager.d.ts +2 -13
  56. package/dist/hooks/useInputManager.d.ts.map +1 -1
  57. package/dist/hooks/useInputManager.js +8 -57
  58. package/dist/hooks/usePluginManager.d.ts.map +1 -1
  59. package/dist/hooks/usePluginManager.js +8 -4
  60. package/dist/hooks/useTasks.d.ts +2 -0
  61. package/dist/hooks/useTasks.d.ts.map +1 -0
  62. package/dist/hooks/useTasks.js +5 -0
  63. package/dist/managers/InputManager.d.ts +5 -28
  64. package/dist/managers/InputManager.d.ts.map +1 -1
  65. package/dist/managers/InputManager.js +26 -127
  66. package/package.json +9 -10
  67. package/src/commands/plugin/uninstall.ts +1 -1
  68. package/src/components/App.tsx +50 -3
  69. package/src/components/{BashShellManager.tsx → BackgroundTaskManager.tsx} +79 -73
  70. package/src/components/ChatInterface.tsx +79 -23
  71. package/src/components/CommandSelector.tsx +38 -20
  72. package/src/components/CompressDisplay.tsx +5 -22
  73. package/src/components/ConfirmationDetails.tsx +108 -0
  74. package/src/components/{Confirmation.tsx → ConfirmationSelector.tsx} +162 -187
  75. package/src/components/DiffDisplay.tsx +122 -107
  76. package/src/components/FileSelector.tsx +0 -2
  77. package/src/components/HistorySearch.tsx +45 -21
  78. package/src/components/InputBox.tsx +14 -34
  79. package/src/components/LoadingIndicator.tsx +56 -0
  80. package/src/components/Markdown.tsx +126 -318
  81. package/src/components/MessageItem.tsx +1 -3
  82. package/src/components/MessageList.tsx +10 -67
  83. package/src/components/PlanDisplay.tsx +5 -33
  84. package/src/components/PluginDetail.tsx +1 -1
  85. package/src/components/RewindCommand.tsx +38 -1
  86. package/src/components/SubagentBlock.tsx +28 -14
  87. package/src/components/TaskList.tsx +70 -0
  88. package/src/components/ToolResultDisplay.tsx +6 -2
  89. package/src/contexts/useChat.tsx +82 -60
  90. package/src/hooks/useInputManager.ts +9 -73
  91. package/src/hooks/usePluginManager.ts +10 -4
  92. package/src/hooks/useTasks.ts +6 -0
  93. package/src/managers/InputManager.ts +30 -157
  94. package/dist/components/BashShellManager.d.ts +0 -6
  95. package/dist/components/BashShellManager.d.ts.map +0 -1
  96. package/dist/components/BashShellManager.js +0 -116
  97. package/dist/components/Confirmation.d.ts.map +0 -1
  98. package/dist/components/MemoryDisplay.d.ts +0 -8
  99. package/dist/components/MemoryDisplay.d.ts.map +0 -1
  100. package/dist/components/MemoryDisplay.js +0 -25
  101. package/dist/components/MemoryTypeSelector.d.ts +0 -8
  102. package/dist/components/MemoryTypeSelector.d.ts.map +0 -1
  103. package/dist/components/MemoryTypeSelector.js +0 -38
  104. package/src/components/MemoryDisplay.tsx +0 -62
  105. package/src/components/MemoryTypeSelector.tsx +0 -98
@@ -1,7 +1,7 @@
1
1
  import React, { useMemo } from "react";
2
- import { Box, Text, useStdout } from "ink";
3
- import { marked, type Token, type Tokens } from "marked";
4
- import { highlight } from "cli-highlight";
2
+ import { Box, Text } from "ink";
3
+ import { Renderer, marked, type Tokens } from "marked";
4
+ import chalk from "chalk";
5
5
 
6
6
  export interface MarkdownProps {
7
7
  children: string;
@@ -17,334 +17,142 @@ const unescapeHtml = (html: string) => {
17
17
  .replace(/'/g, "'");
18
18
  };
19
19
 
20
- const InlineRenderer = ({ tokens }: { tokens: Token[] }) => {
21
- return (
22
- <>
23
- {tokens.map((token, index) => {
24
- switch (token.type) {
25
- case "text": {
26
- const t = token as Tokens.Text;
27
- if (t.tokens) {
28
- return <InlineRenderer key={index} tokens={t.tokens} />;
29
- }
30
- return <Text key={index}>{unescapeHtml(t.text)}</Text>;
31
- }
32
- case "strong":
33
- return (
34
- <Text key={index} bold>
35
- {token.tokens ? (
36
- <InlineRenderer tokens={token.tokens} />
37
- ) : (
38
- unescapeHtml((token as Tokens.Strong).text)
39
- )}
40
- </Text>
41
- );
42
- case "em":
43
- return (
44
- <Text key={index} italic>
45
- {token.tokens ? (
46
- <InlineRenderer tokens={token.tokens} />
47
- ) : (
48
- unescapeHtml((token as Tokens.Em).text)
49
- )}
50
- </Text>
51
- );
52
- case "codespan":
53
- return (
54
- <Text key={index} color="yellow">
55
- {unescapeHtml((token as Tokens.Codespan).text)}
56
- </Text>
57
- );
58
- case "link": {
59
- const t = token as Tokens.Link;
60
- return (
61
- <Text key={index}>
62
- <Text color="blue" underline>
63
- {t.tokens ? (
64
- <InlineRenderer tokens={t.tokens} />
65
- ) : (
66
- unescapeHtml(t.text)
67
- )}
68
- </Text>
69
- <Text color="gray"> ({t.href})</Text>
70
- </Text>
71
- );
72
- }
73
- case "br":
74
- return <Text key={index}>{"\n"}</Text>;
75
- case "del":
76
- return (
77
- <Text key={index} strikethrough>
78
- {token.tokens ? (
79
- <InlineRenderer tokens={token.tokens} />
80
- ) : (
81
- unescapeHtml((token as Tokens.Del).text)
82
- )}
83
- </Text>
84
- );
85
- default:
86
- return <Text key={index}>{token.raw}</Text>;
87
- }
88
- })}
89
- </>
90
- );
91
- };
92
-
93
- const TableRenderer = ({ token }: { token: Tokens.Table }) => {
94
- const { stdout } = useStdout();
95
- const terminalWidth = (stdout?.columns || 80) - 2;
20
+ class AnsiRenderer extends Renderer<string> {
21
+ override code({ text, lang }: Tokens.Code): string {
22
+ const prefix = lang ? `\`\`\`${lang}` : "```";
23
+ const suffix = "```";
24
+ return `\n${chalk.gray(prefix)}\n${text}\n${chalk.gray(suffix)}\n`;
25
+ }
96
26
 
97
- const columnWidths = useMemo(() => {
98
- const numCols = token.header.length;
99
- const minWidth = 5;
100
- const maxColWidth = 40;
101
- const widths = token.header.map((h) =>
102
- Math.min(maxColWidth, Math.max(minWidth, h.text.length)),
27
+ override blockquote({ tokens }: Tokens.Blockquote): string {
28
+ const body = this.parser.parse(tokens);
29
+ return (
30
+ "\n" +
31
+ body
32
+ .trim()
33
+ .split("\n")
34
+ .map((line) => chalk.gray("> ") + line)
35
+ .join("\n") +
36
+ "\n"
103
37
  );
38
+ }
104
39
 
105
- token.rows.forEach((row) => {
106
- row.forEach((cell, i) => {
107
- widths[i] = Math.min(
108
- maxColWidth,
109
- Math.max(widths[i] || minWidth, cell.text.length),
110
- );
111
- });
112
- });
40
+ override heading({ tokens, depth }: Tokens.Heading): string {
41
+ const text = this.parser.parseInline(tokens);
42
+ const hashes = "#".repeat(depth);
43
+ return `\n${chalk.cyan(`${hashes} ${text}`)}\n`;
44
+ }
113
45
 
114
- const paddedWidths = widths.map((w) => w + 2);
115
- const totalWidth = paddedWidths.reduce((a, b) => a + b, 0) + numCols + 1;
46
+ override hr(): string {
47
+ return `\n${chalk.gray("─".repeat(20))}\n`;
48
+ }
116
49
 
117
- if (totalWidth <= terminalWidth) {
118
- return paddedWidths;
119
- }
50
+ override list(token: Tokens.List): string {
51
+ const body = token.items
52
+ .map((item, i) => {
53
+ const text = this.listitem(item);
54
+ const prefix = token.ordered
55
+ ? chalk.gray(`${(token.start || 1) + i}. `)
56
+ : chalk.gray("• ");
57
+ const lines = text.split("\n");
58
+ const firstLine = prefix + lines[0];
59
+ const restLines = lines
60
+ .slice(1)
61
+ .filter((line) => line.length > 0)
62
+ .map((line) => " " + line);
63
+ return [firstLine, ...restLines].join("\n") + "\n";
64
+ })
65
+ .join("");
66
+ return `\n${body}`;
67
+ }
120
68
 
121
- // If table is too wide, scale down columns proportionally
122
- const availableWidth = terminalWidth - numCols - 1;
123
- const scaleFactor = availableWidth / (totalWidth - numCols - 1);
124
- return paddedWidths.map((w) =>
125
- Math.max(minWidth, Math.floor(w * scaleFactor)),
126
- );
127
- }, [token, terminalWidth]);
69
+ override listitem(item: Tokens.ListItem): string {
70
+ return `${this.parser.parse(item.tokens).trim()}\n`;
71
+ }
128
72
 
129
- return (
130
- <Box
131
- flexDirection="column"
132
- marginBottom={1}
133
- borderStyle="single"
134
- borderColor="gray"
135
- width={columnWidths.reduce((a, b) => a + b, 0) + token.header.length + 1}
136
- >
137
- {/* Header */}
138
- <Box
139
- flexDirection="row"
140
- borderStyle="single"
141
- borderBottom
142
- borderTop={false}
143
- borderLeft={false}
144
- borderRight={false}
145
- borderColor="gray"
146
- >
147
- {token.header.map((cell, i) => (
148
- <Box
149
- key={i}
150
- width={columnWidths[i]}
151
- paddingX={1}
152
- borderStyle="single"
153
- borderLeft={i > 0}
154
- borderRight={false}
155
- borderTop={false}
156
- borderBottom={false}
157
- borderColor="gray"
158
- >
159
- <Text bold wrap="wrap">
160
- <InlineRenderer tokens={cell.tokens} />
161
- </Text>
162
- </Box>
163
- ))}
164
- </Box>
165
- {/* Rows */}
166
- {token.rows.map((row, rowIndex) => (
167
- <Box key={rowIndex} flexDirection="row">
168
- {row.map((cell, i) => (
169
- <Box
170
- key={i}
171
- width={columnWidths[i]}
172
- paddingX={1}
173
- borderStyle="single"
174
- borderLeft={i > 0}
175
- borderRight={false}
176
- borderTop={false}
177
- borderBottom={false}
178
- borderColor="gray"
179
- >
180
- <Text wrap="wrap">
181
- <InlineRenderer tokens={cell.tokens} />
182
- </Text>
183
- </Box>
184
- ))}
185
- </Box>
186
- ))}
187
- </Box>
188
- );
189
- };
73
+ override checkbox({ checked }: Tokens.Checkbox): string {
74
+ return checked ? chalk.green("[x] ") : chalk.gray("[ ] ");
75
+ }
190
76
 
191
- const BlockRenderer = ({ tokens }: { tokens: Token[] }) => {
192
- return (
193
- <>
194
- {tokens.map((token, index) => {
195
- switch (token.type) {
196
- case "heading": {
197
- const t = token as Tokens.Heading;
198
- return (
199
- <Box key={index} marginBottom={1} flexDirection="column">
200
- <Text bold color="cyan">
201
- {"#".repeat(t.depth)} <InlineRenderer tokens={t.tokens} />
202
- </Text>
203
- </Box>
204
- );
205
- }
206
- case "paragraph": {
207
- const t = token as Tokens.Paragraph;
208
- return (
209
- <Box key={index} marginBottom={1} flexDirection="row">
210
- <Text>
211
- <InlineRenderer tokens={t.tokens} />
212
- </Text>
213
- </Box>
214
- );
215
- }
216
- case "code": {
217
- const t = token as Tokens.Code;
218
- if (t.lang !== undefined) {
219
- const raw = token.raw.endsWith("\n")
220
- ? token.raw.slice(0, -1)
221
- : token.raw;
222
- const lines = raw.split("\n");
223
- const opening = lines[0];
224
- const closing = lines[lines.length - 1];
225
- const content = lines.slice(1, -1).join("\n");
226
- const highlighted = content
227
- ? highlight(unescapeHtml(content), {
228
- language: t.lang,
229
- ignoreIllegals: true,
230
- })
231
- : "";
232
- return (
233
- <Box
234
- key={index}
235
- flexDirection="column"
236
- paddingX={1}
237
- marginBottom={1}
238
- >
239
- <Text color="gray">{opening}</Text>
240
- {highlighted && <Text>{highlighted}</Text>}
241
- <Text color="gray">{closing}</Text>
242
- </Box>
243
- );
244
- }
245
- return (
246
- <Box
247
- key={index}
248
- flexDirection="column"
249
- paddingX={1}
250
- marginBottom={1}
251
- >
252
- <Text>{unescapeHtml(t.text)}</Text>
253
- </Box>
254
- );
255
- }
256
- case "list": {
257
- const t = token as Tokens.List;
258
- return (
259
- <Box
260
- key={index}
261
- flexDirection="column"
262
- marginBottom={1}
263
- paddingLeft={2}
264
- >
265
- {t.items.map((item, i) => {
266
- const start = t.start || 1;
267
- return (
268
- <Box key={i} flexDirection="row">
269
- <Text color="gray">
270
- {t.ordered ? `${start + i}. ` : "• "}
271
- </Text>
272
- <Box flexDirection="column" flexGrow={1}>
273
- {item.tokens.map((itemToken, itemIndex) => {
274
- if (itemToken.type === "text") {
275
- const it = itemToken as Tokens.Text;
276
- return (
277
- <Box key={itemIndex} flexDirection="row">
278
- <Text>
279
- <InlineRenderer
280
- tokens={it.tokens || [itemToken]}
281
- />
282
- </Text>
283
- </Box>
284
- );
285
- }
286
- return (
287
- <BlockRenderer
288
- key={itemIndex}
289
- tokens={[itemToken]}
290
- />
291
- );
292
- })}
293
- </Box>
294
- </Box>
295
- );
296
- })}
297
- </Box>
298
- );
299
- }
300
- case "blockquote": {
301
- const t = token as Tokens.Blockquote;
302
- return (
303
- <Box
304
- key={index}
305
- flexDirection="column"
306
- paddingLeft={2}
307
- borderStyle="single"
308
- borderLeft
309
- borderRight={false}
310
- borderTop={false}
311
- borderBottom={false}
312
- borderColor="gray"
313
- marginBottom={1}
314
- >
315
- <BlockRenderer tokens={t.tokens} />
316
- </Box>
317
- );
318
- }
319
- case "hr":
320
- return (
321
- <Box key={index} marginBottom={1}>
322
- <Text color="gray">{"─".repeat(20)}</Text>
323
- </Box>
324
- );
325
- case "table":
326
- return <TableRenderer key={index} token={token as Tokens.Table} />;
327
- case "space":
328
- return null;
329
- default:
330
- return (
331
- <Box key={index} marginBottom={1}>
332
- <Text>{token.raw}</Text>
333
- </Box>
334
- );
335
- }
336
- })}
337
- </>
338
- );
339
- };
77
+ override paragraph({ tokens }: Tokens.Paragraph): string {
78
+ const text = this.parser.parseInline(tokens);
79
+ return `\n${text}\n`;
80
+ }
81
+
82
+ override table(token: Tokens.Table): string {
83
+ const header = token.header.map((cell) => this.tablecell(cell)).join("");
84
+ const body = token.rows
85
+ .map((row) => row.map((cell) => this.tablecell(cell)).join("") + "\n")
86
+ .join("");
87
+ return `\n${header}\n${body}\n`;
88
+ }
89
+
90
+ override tablerow({ text }: Tokens.TableRow): string {
91
+ return text + "\n";
92
+ }
93
+
94
+ override tablecell(token: Tokens.TableCell): string {
95
+ const text = token.header ? chalk.bold(token.text) : token.text;
96
+ return text + " | ";
97
+ }
98
+
99
+ override strong({ tokens }: Tokens.Strong): string {
100
+ const text = this.parser.parseInline(tokens);
101
+ return chalk.bold(text);
102
+ }
103
+
104
+ override em({ tokens }: Tokens.Em): string {
105
+ const text = this.parser.parseInline(tokens);
106
+ return chalk.italic(text);
107
+ }
108
+
109
+ override codespan({ text }: Tokens.Codespan): string {
110
+ return chalk.yellow(text);
111
+ }
112
+
113
+ override br(): string {
114
+ return "\n";
115
+ }
116
+
117
+ override del({ tokens }: Tokens.Del): string {
118
+ const text = this.parser.parseInline(tokens);
119
+ return chalk.strikethrough(text);
120
+ }
121
+
122
+ override link({ href, tokens }: Tokens.Link): string {
123
+ const text = this.parser.parseInline(tokens);
124
+ const linkText = chalk.blue.underline(text);
125
+ const hrefText = chalk.gray(`(${href})`);
126
+ return `${linkText} ${hrefText}`;
127
+ }
128
+
129
+ override image({ href, tokens, text }: Tokens.Image): string {
130
+ const alt = this.parser.parseInline(tokens) || text;
131
+ return chalk.gray(`![${alt}](${href})`);
132
+ }
133
+
134
+ override text(token: Tokens.Text | Tokens.Escape): string {
135
+ return "tokens" in token && token.tokens
136
+ ? this.parser.parseInline(token.tokens)
137
+ : unescapeHtml(token.text);
138
+ }
139
+ }
140
+
141
+ const renderer = new AnsiRenderer();
340
142
 
341
- // Markdown component using custom Ink-based renderer
143
+ // Markdown component using custom ANSI renderer
342
144
  export const Markdown = React.memo(({ children }: MarkdownProps) => {
343
- const tokens = useMemo(() => marked.lexer(children), [children]);
145
+ const ansiContent = useMemo(() => {
146
+ return marked.parse(children, {
147
+ renderer,
148
+ gfm: true,
149
+ breaks: true,
150
+ }) as string;
151
+ }, [children]);
344
152
 
345
153
  return (
346
154
  <Box flexDirection="column">
347
- <BlockRenderer tokens={tokens} />
155
+ <Text>{ansiContent.trim()}</Text>
348
156
  </Box>
349
157
  );
350
158
  });
@@ -4,7 +4,6 @@ import type { Message } from "wave-agent-sdk";
4
4
  import { MessageSource } from "wave-agent-sdk";
5
5
  import { CommandOutputDisplay } from "./CommandOutputDisplay.js";
6
6
  import { ToolResultDisplay } from "./ToolResultDisplay.js";
7
- import { MemoryDisplay } from "./MemoryDisplay.js";
8
7
  import { CompressDisplay } from "./CompressDisplay.js";
9
8
  import { SubagentBlock } from "./SubagentBlock.js";
10
9
  import { ReasoningDisplay } from "./ReasoningDisplay.js";
@@ -22,6 +21,7 @@ export const MessageItem = ({
22
21
  shouldShowHeader,
23
22
  }: MessageItemProps) => {
24
23
  if (message.blocks.length === 0) return null;
24
+
25
25
  return (
26
26
  <Box flexDirection="column" gap={1} marginTop={1}>
27
27
  {shouldShowHeader && (
@@ -79,8 +79,6 @@ export const MessageItem = ({
79
79
  </Box>
80
80
  )}
81
81
 
82
- {block.type === "memory" && <MemoryDisplay block={block} />}
83
-
84
82
  {block.type === "compress" && (
85
83
  <CompressDisplay block={block} isExpanded={isExpanded} />
86
84
  )}
@@ -7,9 +7,8 @@ export interface MessageListProps {
7
7
  messages: Message[];
8
8
  isLoading?: boolean;
9
9
  isCommandRunning?: boolean;
10
- isCompressing?: boolean;
11
- latestTotalTokens?: number;
12
10
  isExpanded?: boolean;
11
+ forceStaticLastMessage?: boolean;
13
12
  }
14
13
 
15
14
  export const MessageList = React.memo(
@@ -17,15 +16,16 @@ export const MessageList = React.memo(
17
16
  messages,
18
17
  isLoading = false,
19
18
  isCommandRunning = false,
20
- isCompressing = false,
21
- latestTotalTokens = 0,
22
19
  isExpanded = false,
20
+ forceStaticLastMessage = false,
23
21
  }: MessageListProps) => {
24
22
  // Empty message state
25
23
  if (messages.length === 0) {
26
24
  return (
27
- <Box flexDirection="column" paddingY={1}>
28
- <Text color="gray">Welcome to WAVE Code Assistant!</Text>
25
+ <Box flexDirection="column" gap={1}>
26
+ <Box flexDirection="column" paddingY={1}>
27
+ <Text color="gray">Welcome to WAVE Code Assistant!</Text>
28
+ </Box>
29
29
  </Box>
30
30
  );
31
31
  }
@@ -42,7 +42,8 @@ export const MessageList = React.memo(
42
42
  : 0;
43
43
 
44
44
  // Compute which messages to render statically vs dynamically
45
- const shouldRenderLastDynamic = isLoading || isCommandRunning;
45
+ const shouldRenderLastDynamic =
46
+ !forceStaticLastMessage && (isLoading || isCommandRunning);
46
47
  const staticMessages = shouldRenderLastDynamic
47
48
  ? displayMessages.slice(0, -1)
48
49
  : displayMessages;
@@ -52,7 +53,7 @@ export const MessageList = React.memo(
52
53
  : [];
53
54
 
54
55
  return (
55
- <Box flexDirection="column" gap={1}>
56
+ <Box flexDirection="column" gap={1} paddingBottom={1}>
56
57
  {/* Show omitted message count when limiting */}
57
58
  {omittedCount > 0 && (
58
59
  <Box>
@@ -86,7 +87,7 @@ export const MessageList = React.memo(
86
87
  const previousMessage =
87
88
  messageIndex > 0 ? displayMessages[messageIndex - 1] : undefined;
88
89
  return (
89
- <Box key={`dynamic-${index}`} marginTop={-1}>
90
+ <Box key={`dynamic-${index}`}>
90
91
  <MessageItem
91
92
  message={message}
92
93
  shouldShowHeader={previousMessage?.role !== message.role}
@@ -95,64 +96,6 @@ export const MessageList = React.memo(
95
96
  </Box>
96
97
  );
97
98
  })}
98
-
99
- {(isLoading || isCommandRunning || isCompressing) && (
100
- <Box flexDirection="column" gap={1}>
101
- {isLoading && (
102
- <Box>
103
- <Text color="yellow">💭 AI is thinking... </Text>
104
- <Text color="gray" dimColor>
105
- |{" "}
106
- </Text>
107
- <Text color="red" bold>
108
- Esc
109
- </Text>
110
- <Text color="gray" dimColor>
111
- {" "}
112
- to abort
113
- </Text>
114
- </Box>
115
- )}
116
- {isCommandRunning && (
117
- <Text color="blue">🚀 Command is running...</Text>
118
- )}
119
- {isCompressing && (
120
- <Text color="magenta">🗜️ Compressing message history...</Text>
121
- )}
122
- </Box>
123
- )}
124
-
125
- {/* Bottom info and shortcut key hints */}
126
- {messages.length > 0 && (
127
- <Box>
128
- <Box justifyContent="space-between" width="100%">
129
- <Box>
130
- <Text color="gray">
131
- Messages {messages.length}
132
- {latestTotalTokens > 0 && (
133
- <>
134
- <Text color="gray" dimColor>
135
- {" "}
136
- |{" "}
137
- </Text>
138
- <Text color="blue" bold>
139
- {latestTotalTokens.toLocaleString()}
140
- </Text>
141
- <Text color="gray" dimColor>
142
- {" "}
143
- tokens
144
- </Text>
145
- </>
146
- )}
147
- </Text>
148
- </Box>
149
- <Text color="gray" dimColor>
150
- <Text color="cyan">Ctrl+O</Text> Toggle{" "}
151
- {isExpanded ? "Collapse" : "Expand"}
152
- </Text>
153
- </Box>
154
- </Box>
155
- )}
156
99
  </Box>
157
100
  );
158
101
  },
@@ -1,5 +1,5 @@
1
- import React, { useMemo } from "react";
2
- import { Box, Text, useStdout } from "ink";
1
+ import React from "react";
2
+ import { Box } from "ink";
3
3
  import { Markdown } from "./Markdown.js";
4
4
 
5
5
  interface PlanDisplayProps {
@@ -7,39 +7,11 @@ interface PlanDisplayProps {
7
7
  isExpanded?: boolean;
8
8
  }
9
9
 
10
- export const PlanDisplay: React.FC<PlanDisplayProps> = ({
11
- plan,
12
- isExpanded = false,
13
- }) => {
14
- const { stdout } = useStdout();
15
- const maxHeight = useMemo(() => {
16
- // Similar to DiffDisplay.tsx maxHeight calculation
17
- return Math.max(5, (stdout?.rows || 24) - 20);
18
- }, [stdout?.rows]);
19
-
20
- const lines = useMemo(() => plan.split("\n"), [plan]);
21
- const isOverflowing = !isExpanded && lines.length > maxHeight;
22
-
10
+ export const PlanDisplay: React.FC<PlanDisplayProps> = ({ plan }) => {
23
11
  return (
24
12
  <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
- )}
13
+ <Box flexDirection="column">
14
+ <Markdown>{plan}</Markdown>
43
15
  </Box>
44
16
  </Box>
45
17
  );
@@ -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;