wave-code 0.10.4 → 0.11.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 (57) hide show
  1. package/dist/acp/agent.d.ts +1 -0
  2. package/dist/acp/agent.d.ts.map +1 -1
  3. package/dist/acp/agent.js +122 -18
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +2 -2
  6. package/dist/components/App.d.ts.map +1 -1
  7. package/dist/components/App.js +4 -4
  8. package/dist/components/BackgroundTaskManager.d.ts.map +1 -1
  9. package/dist/components/BackgroundTaskManager.js +3 -2
  10. package/dist/components/ChatInterface.d.ts.map +1 -1
  11. package/dist/components/ChatInterface.js +2 -1
  12. package/dist/components/ConfirmationDetails.d.ts.map +1 -1
  13. package/dist/components/ConfirmationDetails.js +2 -2
  14. package/dist/components/ConfirmationSelector.d.ts.map +1 -1
  15. package/dist/components/ConfirmationSelector.js +20 -4
  16. package/dist/components/InputBox.d.ts +1 -1
  17. package/dist/components/InputBox.d.ts.map +1 -1
  18. package/dist/components/InputBox.js +1 -2
  19. package/dist/components/MessageList.d.ts +2 -1
  20. package/dist/components/MessageList.d.ts.map +1 -1
  21. package/dist/components/MessageList.js +18 -17
  22. package/dist/constants/commands.d.ts.map +1 -1
  23. package/dist/constants/commands.js +0 -6
  24. package/dist/contexts/useChat.d.ts +3 -2
  25. package/dist/contexts/useChat.d.ts.map +1 -1
  26. package/dist/contexts/useChat.js +32 -28
  27. package/dist/hooks/useInputManager.d.ts.map +1 -1
  28. package/dist/hooks/useInputManager.js +4 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +40 -1
  31. package/dist/managers/inputHandlers.d.ts.map +1 -1
  32. package/dist/managers/inputHandlers.js +7 -8
  33. package/dist/managers/inputReducer.d.ts +2 -3
  34. package/dist/managers/inputReducer.d.ts.map +1 -1
  35. package/dist/managers/inputReducer.js +4 -4
  36. package/dist/print-cli.d.ts.map +1 -1
  37. package/dist/print-cli.js +5 -3
  38. package/dist/types.d.ts +2 -0
  39. package/dist/types.d.ts.map +1 -1
  40. package/package.json +2 -2
  41. package/src/acp/agent.ts +147 -21
  42. package/src/cli.tsx +4 -0
  43. package/src/components/App.tsx +8 -0
  44. package/src/components/BackgroundTaskManager.tsx +17 -1
  45. package/src/components/ChatInterface.tsx +4 -1
  46. package/src/components/ConfirmationDetails.tsx +5 -1
  47. package/src/components/ConfirmationSelector.tsx +18 -4
  48. package/src/components/InputBox.tsx +1 -2
  49. package/src/components/MessageList.tsx +21 -18
  50. package/src/constants/commands.ts +0 -6
  51. package/src/contexts/useChat.tsx +49 -35
  52. package/src/hooks/useInputManager.ts +4 -1
  53. package/src/index.ts +43 -1
  54. package/src/managers/inputHandlers.ts +8 -10
  55. package/src/managers/inputReducer.ts +6 -6
  56. package/src/print-cli.ts +6 -2
  57. package/src/types.ts +2 -0
package/src/acp/agent.ts CHANGED
@@ -9,6 +9,10 @@ import {
9
9
  listAllSessions as listAllWaveSessions,
10
10
  deleteSession as deleteWaveSession,
11
11
  truncateContent,
12
+ BASH_TOOL_NAME,
13
+ EDIT_TOOL_NAME,
14
+ WRITE_TOOL_NAME,
15
+ EXIT_PLAN_MODE_TOOL_NAME,
12
16
  } from "wave-agent-sdk";
13
17
  import * as fs from "node:fs/promises";
14
18
  import * as path from "node:path";
@@ -39,6 +43,10 @@ import {
39
43
  type SetSessionModeRequest,
40
44
  type SetSessionConfigOptionRequest,
41
45
  type SetSessionConfigOptionResponse,
46
+ type TextContent,
47
+ type ResourceLink,
48
+ type EmbeddedResource,
49
+ type ImageContent,
42
50
  AGENT_METHODS,
43
51
  } from "@agentclientprotocol/sdk";
44
52
 
@@ -74,6 +82,12 @@ export class WaveAcpAgent implements AcpAgent {
74
82
  name: "Bypass Permissions",
75
83
  description: "Automatically accept all tool calls",
76
84
  },
85
+ {
86
+ id: "dontAsk",
87
+ name: "Don't Ask",
88
+ description:
89
+ "Automatically deny restricted tools unless pre-approved",
90
+ },
77
91
  ],
78
92
  };
79
93
  }
@@ -91,6 +105,7 @@ export class WaveAcpAgent implements AcpAgent {
91
105
  { value: "acceptEdits", name: "Accept Edits" },
92
106
  { value: "plan", name: "Plan" },
93
107
  { value: "bypassPermissions", name: "Bypass Permissions" },
108
+ { value: "dontAsk", name: "Don't Ask" },
94
109
  ],
95
110
  },
96
111
  ];
@@ -121,6 +136,10 @@ export class WaveAcpAgent implements AcpAgent {
121
136
  list: {},
122
137
  close: {},
123
138
  },
139
+ promptCapabilities: {
140
+ image: true,
141
+ embeddedContext: true,
142
+ },
124
143
  },
125
144
  };
126
145
  }
@@ -139,7 +158,7 @@ export class WaveAcpAgent implements AcpAgent {
139
158
  const agent = await WaveAgent.create({
140
159
  workdir: cwd,
141
160
  restoreSessionId: sessionId,
142
- stream: false,
161
+ stream: true,
143
162
  canUseTool: (context) => {
144
163
  if (!agentRef.instance) {
145
164
  throw new Error("Agent instance not yet initialized");
@@ -274,7 +293,12 @@ export class WaveAcpAgent implements AcpAgent {
274
293
  const agent = this.agents.get(sessionId);
275
294
  if (!agent) throw new Error(`Session ${sessionId} not found`);
276
295
  agent.setPermissionMode(
277
- modeId as "default" | "acceptEdits" | "plan" | "bypassPermissions",
296
+ modeId as
297
+ | "default"
298
+ | "acceptEdits"
299
+ | "plan"
300
+ | "bypassPermissions"
301
+ | "dontAsk",
278
302
  );
279
303
  }
280
304
 
@@ -287,7 +311,12 @@ export class WaveAcpAgent implements AcpAgent {
287
311
 
288
312
  if (configId === "permission_mode") {
289
313
  agent.setPermissionMode(
290
- value as "default" | "acceptEdits" | "plan" | "bypassPermissions",
314
+ value as
315
+ | "default"
316
+ | "acceptEdits"
317
+ | "plan"
318
+ | "bypassPermissions"
319
+ | "dontAsk",
291
320
  );
292
321
  }
293
322
 
@@ -299,6 +328,7 @@ export class WaveAcpAgent implements AcpAgent {
299
328
  async prompt(params: PromptRequest): Promise<PromptResponse> {
300
329
  const { sessionId, prompt } = params;
301
330
  logger.info(`Received prompt for session ${sessionId}`);
331
+ logger.debug(`Prompt content for session ${sessionId}:`, prompt);
302
332
  const agent = this.agents.get(sessionId);
303
333
  if (!agent) {
304
334
  logger.error(`Session ${sessionId} not found`);
@@ -306,25 +336,32 @@ export class WaveAcpAgent implements AcpAgent {
306
336
  }
307
337
 
308
338
  // Map ACP prompt to Wave Agent sendMessage
309
- const textContent = prompt
310
- .filter((block) => block.type === "text")
311
- .map((block) => (block as { text: string }).text)
312
- .join("\n");
313
-
314
- const images = prompt
315
- .filter((block) => block.type === "image")
316
- .map((block) => {
317
- const img = block as { data: string; mimeType: string };
318
- return {
319
- path: `data:${img.mimeType};base64,${img.data}`,
339
+ const textBlocks: string[] = [];
340
+ const images: { path: string; mimeType: string }[] = [];
341
+
342
+ for (const block of prompt) {
343
+ if (block.type === "text") {
344
+ textBlocks.push((block as TextContent).text);
345
+ } else if (block.type === "resource_link") {
346
+ const link = block as ResourceLink;
347
+ textBlocks.push(`[${link.name}](${link.uri})`);
348
+ } else if (block.type === "resource") {
349
+ const embedded = block as EmbeddedResource;
350
+ textBlocks.push(`[Resource](${embedded.resource.uri})`);
351
+ } else if (block.type === "image") {
352
+ const img = block as ImageContent;
353
+ images.push({
354
+ path: img.data.startsWith("data:")
355
+ ? img.data
356
+ : `data:${img.mimeType};base64,${img.data}`,
320
357
  mimeType: img.mimeType,
321
- };
322
- });
358
+ });
359
+ }
360
+ }
361
+
362
+ const textContent = textBlocks.join("");
323
363
 
324
364
  try {
325
- logger.info(
326
- `Sending message to agent: ${textContent.substring(0, 50)}...`,
327
- );
328
365
  await agent.sendMessage(
329
366
  textContent,
330
367
  images.length > 0 ? images : undefined,
@@ -354,6 +391,33 @@ export class WaveAcpAgent implements AcpAgent {
354
391
  }
355
392
  }
356
393
 
394
+ private getAllowAlwaysName(context: ToolPermissionContext): string {
395
+ if (context.toolName === BASH_TOOL_NAME) {
396
+ const command = (context.toolInput?.command as string) || "";
397
+ if (command.startsWith("mkdir")) {
398
+ return "Yes, and auto-accept edits";
399
+ }
400
+ if (context.suggestedPrefix) {
401
+ const prefix =
402
+ context.suggestedPrefix.length > 12
403
+ ? context.suggestedPrefix.substring(0, 9) + "..."
404
+ : context.suggestedPrefix;
405
+ return `Yes, always allow ${prefix}`;
406
+ }
407
+ return "Yes, always allow this command";
408
+ }
409
+ if (
410
+ context.toolName === EDIT_TOOL_NAME ||
411
+ context.toolName === WRITE_TOOL_NAME
412
+ ) {
413
+ return "Yes, and auto-accept edits";
414
+ }
415
+ if (context.toolName === EXIT_PLAN_MODE_TOOL_NAME) {
416
+ return "Yes, auto-accept edits";
417
+ }
418
+ return "Allow Always";
419
+ }
420
+
357
421
  private async handlePermissionRequest(
358
422
  sessionId: string,
359
423
  context: ToolPermissionContext,
@@ -390,7 +454,7 @@ export class WaveAcpAgent implements AcpAgent {
390
454
  ? `${effectiveName}: ${effectiveCompactParams}`
391
455
  : effectiveName || "Tool Call";
392
456
 
393
- const options: PermissionOption[] = [
457
+ let options: PermissionOption[] = [
394
458
  {
395
459
  optionId: "allow_once",
396
460
  name: "Allow Once",
@@ -408,6 +472,38 @@ export class WaveAcpAgent implements AcpAgent {
408
472
  },
409
473
  ];
410
474
 
475
+ if (
476
+ context.toolName === BASH_TOOL_NAME ||
477
+ context.toolName === EDIT_TOOL_NAME ||
478
+ context.toolName === WRITE_TOOL_NAME
479
+ ) {
480
+ options = [
481
+ {
482
+ optionId: "allow_once",
483
+ name: "Yes, proceed",
484
+ kind: "allow_once",
485
+ },
486
+ {
487
+ optionId: "allow_always",
488
+ name: this.getAllowAlwaysName(context),
489
+ kind: "allow_always",
490
+ },
491
+ ];
492
+ } else if (context.toolName === EXIT_PLAN_MODE_TOOL_NAME) {
493
+ options = [
494
+ {
495
+ optionId: "allow_once",
496
+ name: "Yes, manually approve edits",
497
+ kind: "allow_once",
498
+ },
499
+ {
500
+ optionId: "allow_always",
501
+ name: "Yes, auto-accept edits",
502
+ kind: "allow_always",
503
+ },
504
+ ];
505
+ }
506
+
411
507
  const content = context.toolName
412
508
  ? await this.getToolContentAsync(
413
509
  context.toolName,
@@ -446,11 +542,33 @@ export class WaveAcpAgent implements AcpAgent {
446
542
 
447
543
  switch (selectedOptionId) {
448
544
  case "allow_always":
545
+ if (context.toolName === BASH_TOOL_NAME) {
546
+ const command = (context.toolInput?.command as string) || "";
547
+ const rule = context.suggestedPrefix
548
+ ? `${context.suggestedPrefix}*`
549
+ : command;
550
+ return {
551
+ behavior: "allow",
552
+ newPermissionRule: `${BASH_TOOL_NAME}(${rule})`,
553
+ };
554
+ }
555
+ if (
556
+ context.toolName === EDIT_TOOL_NAME ||
557
+ context.toolName === WRITE_TOOL_NAME
558
+ ) {
559
+ return {
560
+ behavior: "allow",
561
+ newPermissionMode: "acceptEdits",
562
+ };
563
+ }
449
564
  return {
450
565
  behavior: "allow",
451
- newPermissionRule: `${context.toolName}(*)`,
566
+ newPermissionRule: context.toolName,
452
567
  };
453
568
  case "allow_once":
569
+ if (context.toolName === EXIT_PLAN_MODE_TOOL_NAME) {
570
+ return { behavior: "allow", newPermissionMode: "default" };
571
+ }
454
572
  return { behavior: "allow" };
455
573
  case "reject_once":
456
574
  return { behavior: "deny", message: "Rejected by user" };
@@ -564,6 +682,14 @@ export class WaveAcpAgent implements AcpAgent {
564
682
  oldText: parameters.old_string as string,
565
683
  newText: parameters.new_string as string,
566
684
  });
685
+ } else if (name === EXIT_PLAN_MODE_TOOL_NAME) {
686
+ contents.push({
687
+ type: "content",
688
+ content: {
689
+ type: "text",
690
+ text: parameters.plan_content as string,
691
+ },
692
+ });
567
693
  }
568
694
  }
569
695
 
package/src/cli.tsx CHANGED
@@ -18,6 +18,8 @@ export async function startCli(options: CliOptions): Promise<void> {
18
18
  permissionMode,
19
19
  pluginDirs,
20
20
  tools,
21
+ allowedTools,
22
+ disallowedTools,
21
23
  worktreeSession,
22
24
  workdir,
23
25
  version,
@@ -41,6 +43,8 @@ export async function startCli(options: CliOptions): Promise<void> {
41
43
  permissionMode={permissionMode}
42
44
  pluginDirs={pluginDirs}
43
45
  tools={tools}
46
+ allowedTools={allowedTools}
47
+ disallowedTools={disallowedTools}
44
48
  worktreeSession={worktreeSession}
45
49
  workdir={workdir}
46
50
  version={version}
@@ -26,6 +26,8 @@ const AppWithProviders: React.FC<AppWithProvidersProps> = ({
26
26
  permissionMode,
27
27
  pluginDirs,
28
28
  tools,
29
+ allowedTools,
30
+ disallowedTools,
29
31
  worktreeSession,
30
32
  workdir,
31
33
  version,
@@ -98,6 +100,8 @@ const AppWithProviders: React.FC<AppWithProvidersProps> = ({
98
100
  permissionMode={permissionMode}
99
101
  pluginDirs={pluginDirs}
100
102
  tools={tools}
103
+ allowedTools={allowedTools}
104
+ disallowedTools={disallowedTools}
101
105
  workdir={workdir}
102
106
  worktreeSession={worktreeSession}
103
107
  version={version}
@@ -162,6 +166,8 @@ export const App: React.FC<AppProps> = ({
162
166
  permissionMode,
163
167
  pluginDirs,
164
168
  tools,
169
+ allowedTools,
170
+ disallowedTools,
165
171
  worktreeSession,
166
172
  workdir,
167
173
  version,
@@ -178,6 +184,8 @@ export const App: React.FC<AppProps> = ({
178
184
  permissionMode={permissionMode}
179
185
  pluginDirs={pluginDirs}
180
186
  tools={tools}
187
+ allowedTools={allowedTools}
188
+ disallowedTools={disallowedTools}
181
189
  worktreeSession={worktreeSession}
182
190
  workdir={workdir}
183
191
  version={version}
@@ -10,6 +10,7 @@ interface Task {
10
10
  startTime: number;
11
11
  exitCode?: number;
12
12
  runtime?: number;
13
+ outputPath?: string;
13
14
  }
14
15
 
15
16
  export interface BackgroundTaskManagerProps {
@@ -30,6 +31,7 @@ export const BackgroundTaskManager: React.FC<BackgroundTaskManagerProps> = ({
30
31
  stdout: string;
31
32
  stderr: string;
32
33
  status: string;
34
+ outputPath?: string;
33
35
  } | null>(null);
34
36
 
35
37
  // Convert backgroundTasks to local Task format
@@ -43,6 +45,7 @@ export const BackgroundTaskManager: React.FC<BackgroundTaskManagerProps> = ({
43
45
  startTime: task.startTime,
44
46
  exitCode: task.exitCode,
45
47
  runtime: task.runtime,
48
+ outputPath: task.outputPath,
46
49
  })),
47
50
  );
48
51
  }, [backgroundTasks]);
@@ -189,6 +192,13 @@ export const BackgroundTaskManager: React.FC<BackgroundTaskManagerProps> = ({
189
192
  )}
190
193
  </Text>
191
194
  </Box>
195
+ {task.outputPath && (
196
+ <Box>
197
+ <Text>
198
+ <Text color="blue">Log File:</Text> {task.outputPath}
199
+ </Text>
200
+ </Box>
201
+ )}
192
202
  </Box>
193
203
 
194
204
  {detailOutput.stdout && (
@@ -313,7 +323,13 @@ export const BackgroundTaskManager: React.FC<BackgroundTaskManagerProps> = ({
313
323
  {isSelected && (
314
324
  <Box marginLeft={4} flexDirection="column">
315
325
  <Text color="gray" dimColor>
316
- Started: {formatTime(task.startTime)}
326
+ {task.outputPath ? (
327
+ <Text>
328
+ <Text color="blue">Log File:</Text> {task.outputPath}
329
+ </Text>
330
+ ) : (
331
+ `Started: ${formatTime(task.startTime)}`
332
+ )}
317
333
  {task.runtime !== undefined &&
318
334
  ` | Runtime: ${formatDuration(task.runtime)}`}
319
335
  {task.exitCode !== undefined && ` | Exit: ${task.exitCode}`}
@@ -45,6 +45,8 @@ export const ChatInterface: React.FC = () => {
45
45
 
46
46
  const model = getModelConfig().model;
47
47
 
48
+ const displayMessages = messages;
49
+
48
50
  const handleDetailsHeightMeasured = useCallback((height: number) => {
49
51
  setDetailsHeight(height);
50
52
  }, []);
@@ -116,9 +118,10 @@ export const ChatInterface: React.FC = () => {
116
118
  return (
117
119
  <Box flexDirection="column">
118
120
  <MessageList
119
- messages={messages}
121
+ messages={displayMessages}
120
122
  isExpanded={isExpanded}
121
123
  forceStatic={isConfirmationVisible && isConfirmationTooTall}
124
+ isFinished={!isLoading && !isCommandRunning && !isCompressing}
122
125
  version={version}
123
126
  workdir={workdir}
124
127
  model={model}
@@ -97,7 +97,11 @@ export const ConfirmationDetails: React.FC<ConfirmationDetailsProps> = ({
97
97
  );
98
98
 
99
99
  if (isStatic) {
100
- return <Static items={[1]}>{() => content}</Static>;
100
+ return (
101
+ <Static items={[1]}>
102
+ {(item) => <React.Fragment key={item}>{content}</React.Fragment>}
103
+ </Static>
104
+ );
101
105
  }
102
106
 
103
107
  return content;
@@ -100,11 +100,18 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
100
100
  return "Yes, auto-accept edits";
101
101
  }
102
102
  if (toolName === BASH_TOOL_NAME) {
103
+ const command = (toolInput?.command as string) || "";
104
+ if (command.trim().startsWith("mkdir")) {
105
+ return "Yes, and auto-accept edits";
106
+ }
103
107
  if (suggestedPrefix) {
104
108
  return `Yes, and don't ask again for: ${suggestedPrefix}`;
105
109
  }
106
110
  return "Yes, and don't ask again for this command in this workdir";
107
111
  }
112
+ if (toolName.startsWith("mcp__")) {
113
+ return `Yes, and don't ask again for: ${toolName}`;
114
+ }
108
115
  return "Yes, and auto-accept edits";
109
116
  };
110
117
 
@@ -363,10 +370,17 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
363
370
  }
364
371
  } else if (state.selectedOption === "auto") {
365
372
  if (toolName === BASH_TOOL_NAME) {
366
- const rule = suggestedPrefix
367
- ? `Bash(${suggestedPrefix}*)`
368
- : `Bash(${toolInput?.command})`;
369
- onDecision({ behavior: "allow", newPermissionRule: rule });
373
+ const command = (toolInput?.command as string) || "";
374
+ if (command.trim().startsWith("mkdir")) {
375
+ onDecision({ behavior: "allow", newPermissionMode: "acceptEdits" });
376
+ } else {
377
+ const rule = suggestedPrefix
378
+ ? `Bash(${suggestedPrefix}*)`
379
+ : `Bash(${toolInput?.command})`;
380
+ onDecision({ behavior: "allow", newPermissionRule: rule });
381
+ }
382
+ } else if (toolName.startsWith("mcp__")) {
383
+ onDecision({ behavior: "allow", newPermissionRule: toolName });
370
384
  } else {
371
385
  onDecision({ behavior: "allow", newPermissionMode: "acceptEdits" });
372
386
  }
@@ -30,6 +30,7 @@ export interface InputBoxProps {
30
30
  sendMessage?: (
31
31
  message: string,
32
32
  images?: Array<{ path: string; mimeType: string }>,
33
+ longTextMap?: Record<string, string>,
33
34
  ) => void;
34
35
  abortMessage?: () => void;
35
36
  // MCP related properties
@@ -57,7 +58,6 @@ export const InputBox: React.FC<InputBoxProps> = ({
57
58
  backgroundCurrentTask,
58
59
  messages,
59
60
  getFullMessageThread,
60
- clearMessages,
61
61
  sessionId,
62
62
  workingDirectory,
63
63
  } = useChat();
@@ -113,7 +113,6 @@ export const InputBox: React.FC<InputBoxProps> = ({
113
113
  onAbortMessage: abortMessage,
114
114
  onBackgroundCurrentTask: backgroundCurrentTask,
115
115
  onPermissionModeChange: setChatPermissionMode,
116
- onClearMessages: clearMessages,
117
116
  sessionId,
118
117
  workdir: workingDirectory,
119
118
  getFullMessageThread,
@@ -8,6 +8,7 @@ export interface MessageListProps {
8
8
  messages: Message[];
9
9
  isExpanded?: boolean;
10
10
  forceStatic?: boolean;
11
+ isFinished?: boolean;
11
12
  version?: string;
12
13
  workdir?: string;
13
14
  model?: string;
@@ -19,6 +20,7 @@ export const MessageList = React.memo(
19
20
  messages,
20
21
  isExpanded = false,
21
22
  forceStatic = false,
23
+ isFinished = false,
22
24
  version,
23
25
  workdir,
24
26
  model,
@@ -38,22 +40,15 @@ export const MessageList = React.memo(
38
40
  </Box>
39
41
  );
40
42
 
41
- // Limit messages when expanded to prevent long rendering times
42
- const maxExpandedMessages = 20;
43
- const shouldLimitMessages =
44
- isExpanded && messages.length > maxExpandedMessages;
45
- const displayMessages = shouldLimitMessages
46
- ? messages.slice(-maxExpandedMessages)
47
- : messages;
43
+ // Limit messages to prevent long rendering times
44
+ const maxMessages = 10;
48
45
 
49
46
  // Flatten messages into blocks with metadata
50
- const allBlocks = displayMessages.flatMap((message, index) => {
51
- const messageIndex = shouldLimitMessages
52
- ? messages.length - maxExpandedMessages + index
53
- : index;
47
+ const allBlocks = messages.flatMap((message, messageIndex) => {
54
48
  return message.blocks.map((block, blockIndex) => ({
55
49
  block,
56
50
  message,
51
+ messageIndex,
57
52
  isLastMessage: messageIndex === messages.length - 1,
58
53
  // Unique key for each block to help Static component
59
54
  key: `${message.id}-${blockIndex}`,
@@ -62,12 +57,8 @@ export const MessageList = React.memo(
62
57
 
63
58
  // Determine which blocks are static vs dynamic
64
59
  const blocksWithStatus = allBlocks.map((item) => {
65
- const { block, isLastMessage } = item;
66
- const isDynamic =
67
- !forceStatic &&
68
- isLastMessage &&
69
- ((block.type === "tool" && block.stage !== "end") ||
70
- (block.type === "bang" && block.isRunning));
60
+ const { isLastMessage } = item;
61
+ const isDynamic = !forceStatic && !isFinished && isLastMessage;
71
62
  return { ...item, isDynamic };
72
63
  });
73
64
 
@@ -86,7 +77,13 @@ export const MessageList = React.memo(
86
77
  }, [dynamicBlocks, isExpanded, onDynamicBlocksHeightMeasured]);
87
78
 
88
79
  const staticItems = [
89
- { isWelcome: true, key: "welcome", block: undefined, message: undefined },
80
+ {
81
+ isWelcome: true,
82
+ key: "welcome",
83
+ block: undefined,
84
+ message: undefined,
85
+ messageIndex: -1,
86
+ },
90
87
  ...staticBlocks.map((b) => ({ ...b, isWelcome: false })),
91
88
  ];
92
89
 
@@ -103,6 +100,12 @@ export const MessageList = React.memo(
103
100
  </React.Fragment>
104
101
  );
105
102
  }
103
+ if (
104
+ messages.length > maxMessages &&
105
+ item.messageIndex < messages.length - maxMessages
106
+ ) {
107
+ return null;
108
+ }
106
109
  return (
107
110
  <MessageBlockItem
108
111
  key={item.key}
@@ -1,12 +1,6 @@
1
1
  import type { SlashCommand } from "wave-agent-sdk";
2
2
 
3
3
  export const AVAILABLE_COMMANDS: SlashCommand[] = [
4
- {
5
- id: "clear",
6
- name: "clear",
7
- description: "Clear the chat session and terminal",
8
- handler: () => {}, // Handler here won't be used, actual processing is in the hook
9
- },
10
4
  {
11
5
  id: "tasks",
12
6
  name: "tasks",