wave-code 0.5.0 → 0.6.2

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 (112) hide show
  1. package/dist/components/App.d.ts.map +1 -1
  2. package/dist/components/App.js +40 -2
  3. package/dist/components/BackgroundTaskManager.d.ts +6 -0
  4. package/dist/components/BackgroundTaskManager.d.ts.map +1 -0
  5. package/dist/components/{TaskManager.js → BackgroundTaskManager.js} +1 -1
  6. package/dist/components/ChatInterface.d.ts.map +1 -1
  7. package/dist/components/ChatInterface.js +40 -5
  8. package/dist/components/CommandOutputDisplay.d.ts.map +1 -1
  9. package/dist/components/CommandOutputDisplay.js +6 -17
  10. package/dist/components/CommandSelector.d.ts.map +1 -1
  11. package/dist/components/CommandSelector.js +16 -2
  12. package/dist/components/CompressDisplay.d.ts.map +1 -1
  13. package/dist/components/CompressDisplay.js +6 -10
  14. package/dist/components/ConfirmationDetails.d.ts +9 -0
  15. package/dist/components/ConfirmationDetails.d.ts.map +1 -0
  16. package/dist/components/ConfirmationDetails.js +53 -0
  17. package/dist/components/{Confirmation.d.ts → ConfirmationSelector.d.ts} +3 -3
  18. package/dist/components/ConfirmationSelector.d.ts.map +1 -0
  19. package/dist/components/{Confirmation.js → ConfirmationSelector.js} +34 -96
  20. package/dist/components/DiffDisplay.d.ts.map +1 -1
  21. package/dist/components/DiffDisplay.js +48 -1
  22. package/dist/components/FileSelector.d.ts.map +1 -1
  23. package/dist/components/FileSelector.js +2 -2
  24. package/dist/components/HelpView.d.ts +6 -0
  25. package/dist/components/HelpView.d.ts.map +1 -0
  26. package/dist/components/HelpView.js +24 -0
  27. package/dist/components/HistorySearch.d.ts.map +1 -1
  28. package/dist/components/HistorySearch.js +12 -4
  29. package/dist/components/InputBox.d.ts +1 -3
  30. package/dist/components/InputBox.d.ts.map +1 -1
  31. package/dist/components/InputBox.js +14 -17
  32. package/dist/components/LoadingIndicator.d.ts +11 -0
  33. package/dist/components/LoadingIndicator.d.ts.map +1 -0
  34. package/dist/components/LoadingIndicator.js +6 -0
  35. package/dist/components/Markdown.d.ts.map +1 -1
  36. package/dist/components/Markdown.js +114 -121
  37. package/dist/components/MessageItem.d.ts +1 -1
  38. package/dist/components/MessageItem.d.ts.map +1 -1
  39. package/dist/components/MessageItem.js +3 -5
  40. package/dist/components/MessageList.d.ts +2 -3
  41. package/dist/components/MessageList.d.ts.map +1 -1
  42. package/dist/components/MessageList.js +29 -12
  43. package/dist/components/PlanDisplay.d.ts.map +1 -1
  44. package/dist/components/PlanDisplay.js +4 -12
  45. package/dist/components/RewindCommand.d.ts +4 -0
  46. package/dist/components/RewindCommand.d.ts.map +1 -1
  47. package/dist/components/RewindCommand.js +20 -3
  48. package/dist/components/TaskList.d.ts +3 -0
  49. package/dist/components/TaskList.d.ts.map +1 -0
  50. package/dist/components/TaskList.js +40 -0
  51. package/dist/components/ToolDisplay.d.ts +9 -0
  52. package/dist/components/ToolDisplay.d.ts.map +1 -0
  53. package/dist/components/ToolDisplay.js +44 -0
  54. package/dist/contexts/useChat.d.ts +11 -3
  55. package/dist/contexts/useChat.d.ts.map +1 -1
  56. package/dist/contexts/useChat.js +51 -32
  57. package/dist/hooks/useInputManager.d.ts +4 -15
  58. package/dist/hooks/useInputManager.d.ts.map +1 -1
  59. package/dist/hooks/useInputManager.js +20 -65
  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 +8 -30
  64. package/dist/managers/InputManager.d.ts.map +1 -1
  65. package/dist/managers/InputManager.js +38 -144
  66. package/dist/print-cli.d.ts.map +1 -1
  67. package/dist/print-cli.js +11 -30
  68. package/package.json +5 -6
  69. package/src/components/App.tsx +51 -3
  70. package/src/components/{TaskManager.tsx → BackgroundTaskManager.tsx} +4 -2
  71. package/src/components/ChatInterface.tsx +80 -23
  72. package/src/components/CommandOutputDisplay.tsx +16 -38
  73. package/src/components/CommandSelector.tsx +41 -17
  74. package/src/components/CompressDisplay.tsx +5 -22
  75. package/src/components/ConfirmationDetails.tsx +108 -0
  76. package/src/components/{Confirmation.tsx → ConfirmationSelector.tsx} +74 -193
  77. package/src/components/DiffDisplay.tsx +71 -1
  78. package/src/components/FileSelector.tsx +0 -2
  79. package/src/components/HelpView.tsx +59 -0
  80. package/src/components/HistorySearch.tsx +45 -21
  81. package/src/components/InputBox.tsx +51 -63
  82. package/src/components/LoadingIndicator.tsx +56 -0
  83. package/src/components/Markdown.tsx +126 -323
  84. package/src/components/MessageItem.tsx +13 -24
  85. package/src/components/MessageList.tsx +48 -82
  86. package/src/components/PlanDisplay.tsx +4 -27
  87. package/src/components/RewindCommand.tsx +39 -2
  88. package/src/components/TaskList.tsx +58 -0
  89. package/src/components/{ToolResultDisplay.tsx → ToolDisplay.tsx} +8 -18
  90. package/src/contexts/useChat.tsx +73 -41
  91. package/src/hooks/useInputManager.ts +21 -83
  92. package/src/hooks/useTasks.ts +6 -0
  93. package/src/managers/InputManager.ts +43 -179
  94. package/src/print-cli.ts +17 -35
  95. package/dist/components/Confirmation.d.ts.map +0 -1
  96. package/dist/components/MemoryDisplay.d.ts +0 -8
  97. package/dist/components/MemoryDisplay.d.ts.map +0 -1
  98. package/dist/components/MemoryDisplay.js +0 -25
  99. package/dist/components/MemoryTypeSelector.d.ts +0 -8
  100. package/dist/components/MemoryTypeSelector.d.ts.map +0 -1
  101. package/dist/components/MemoryTypeSelector.js +0 -38
  102. package/dist/components/SubagentBlock.d.ts +0 -8
  103. package/dist/components/SubagentBlock.d.ts.map +0 -1
  104. package/dist/components/SubagentBlock.js +0 -70
  105. package/dist/components/TaskManager.d.ts +0 -6
  106. package/dist/components/TaskManager.d.ts.map +0 -1
  107. package/dist/components/ToolResultDisplay.d.ts +0 -9
  108. package/dist/components/ToolResultDisplay.d.ts.map +0 -1
  109. package/dist/components/ToolResultDisplay.js +0 -54
  110. package/src/components/MemoryDisplay.tsx +0 -62
  111. package/src/components/MemoryTypeSelector.tsx +0 -98
  112. package/src/components/SubagentBlock.tsx +0 -143
@@ -18,13 +18,6 @@ export class InputManager {
18
18
  // History search state
19
19
  this.showHistorySearch = false;
20
20
  this.historySearchQuery = "";
21
- // Memory type selector state
22
- this.showMemoryTypeSelector = false;
23
- this.memoryMessage = "";
24
- // Input history state
25
- this.userInputHistory = [];
26
- this.historyIndex = -1;
27
- this.historyBuffer = "";
28
21
  // Paste debounce state
29
22
  this.pasteDebounceTimer = null;
30
23
  this.pasteBuffer = "";
@@ -37,9 +30,10 @@ export class InputManager {
37
30
  this.attachedImages = [];
38
31
  this.imageIdCounter = 1;
39
32
  // Additional UI state
40
- this.showTaskManager = false;
33
+ this.showBackgroundTaskManager = false;
41
34
  this.showMcpManager = false;
42
35
  this.showRewindManager = false;
36
+ this.showHelp = false;
43
37
  // Permission mode state
44
38
  this.permissionMode = "default";
45
39
  // Flag to prevent handleInput conflicts when selector selection occurs
@@ -105,12 +99,6 @@ export class InputManager {
105
99
  moveCursorRight() {
106
100
  this.setCursorPosition(this.cursorPosition + 1);
107
101
  }
108
- moveCursorToStart() {
109
- this.setCursorPosition(0);
110
- }
111
- moveCursorToEnd() {
112
- this.setCursorPosition(this.inputText.length);
113
- }
114
102
  // File selector methods
115
103
  async searchFiles(query) {
116
104
  try {
@@ -220,17 +208,20 @@ export class InputManager {
220
208
  }
221
209
  // If not an agent command or execution failed, check local commands
222
210
  if (!commandExecuted) {
223
- if (command === "tasks" && this.callbacks.onShowTaskManager) {
224
- this.callbacks.onShowTaskManager();
211
+ if (command === "tasks") {
212
+ this.setShowBackgroundTaskManager(true);
225
213
  commandExecuted = true;
226
214
  }
227
- else if (command === "mcp" && this.callbacks.onShowMcpManager) {
228
- this.callbacks.onShowMcpManager();
215
+ else if (command === "mcp") {
216
+ this.setShowMcpManager(true);
229
217
  commandExecuted = true;
230
218
  }
231
- else if (command === "rewind" &&
232
- this.callbacks.onShowRewindManager) {
233
- this.callbacks.onShowRewindManager();
219
+ else if (command === "rewind") {
220
+ this.setShowRewindManager(true);
221
+ commandExecuted = true;
222
+ }
223
+ else if (command === "help") {
224
+ this.setShowHelp(true);
234
225
  commandExecuted = true;
235
226
  }
236
227
  }
@@ -280,79 +271,6 @@ export class InputManager {
280
271
  }
281
272
  return false;
282
273
  }
283
- // Memory type selector methods
284
- activateMemoryTypeSelector(message) {
285
- this.showMemoryTypeSelector = true;
286
- this.memoryMessage = message;
287
- this.callbacks.onMemoryTypeSelectorStateChange?.(true, message);
288
- }
289
- async handleMemoryTypeSelect(type) {
290
- const currentMessage = this.inputText.trim();
291
- if (currentMessage.startsWith("#")) {
292
- await this.callbacks.onSaveMemory?.(currentMessage, type);
293
- }
294
- // Close the selector
295
- this.showMemoryTypeSelector = false;
296
- this.memoryMessage = "";
297
- this.callbacks.onMemoryTypeSelectorStateChange?.(false, "");
298
- // Clear input box
299
- this.clearInput();
300
- }
301
- handleCancelMemoryTypeSelect() {
302
- this.showMemoryTypeSelector = false;
303
- this.memoryMessage = "";
304
- this.callbacks.onMemoryTypeSelectorStateChange?.(false, "");
305
- }
306
- // Input history methods
307
- setUserInputHistory(history) {
308
- this.userInputHistory = history;
309
- }
310
- navigateHistory(direction, currentInput) {
311
- if (this.historyIndex === -1) {
312
- this.historyBuffer = currentInput;
313
- }
314
- if (direction === "up") {
315
- if (this.historyIndex < this.userInputHistory.length - 1) {
316
- this.historyIndex++;
317
- }
318
- }
319
- else {
320
- // Down direction
321
- if (this.historyIndex > 0) {
322
- this.historyIndex--;
323
- }
324
- else if (this.historyIndex === 0) {
325
- // Go from first history item to draft
326
- this.historyIndex = -1;
327
- }
328
- else if (this.historyIndex === -1) {
329
- // Go from draft to empty (beyond history bottom)
330
- this.historyIndex = -2;
331
- }
332
- }
333
- let newInput;
334
- if (this.historyIndex === -1) {
335
- newInput = this.historyBuffer;
336
- }
337
- else if (this.historyIndex === -2) {
338
- // Beyond history bottom, clear input
339
- newInput = "";
340
- }
341
- else {
342
- const historyItem = this.userInputHistory[this.userInputHistory.length - 1 - this.historyIndex];
343
- newInput = historyItem || "";
344
- }
345
- const newCursorPosition = newInput.length;
346
- this.inputText = newInput;
347
- this.cursorPosition = newCursorPosition;
348
- this.callbacks.onInputTextChange?.(newInput);
349
- this.callbacks.onCursorPositionChange?.(newCursorPosition);
350
- return { newInput, newCursorPosition };
351
- }
352
- resetHistoryNavigation() {
353
- this.historyIndex = -1;
354
- this.historyBuffer = "";
355
- }
356
274
  // Getter methods for state
357
275
  isFileSelectorActive() {
358
276
  return this.showFileSelector;
@@ -360,9 +278,6 @@ export class InputManager {
360
278
  isCommandSelectorActive() {
361
279
  return this.showCommandSelector;
362
280
  }
363
- isMemoryTypeSelectorActive() {
364
- return this.showMemoryTypeSelector;
365
- }
366
281
  getFileSelectorState() {
367
282
  return {
368
283
  show: this.showFileSelector,
@@ -378,12 +293,6 @@ export class InputManager {
378
293
  position: this.slashPosition,
379
294
  };
380
295
  }
381
- getMemoryTypeSelectorState() {
382
- return {
383
- show: this.showMemoryTypeSelector,
384
- message: this.memoryMessage,
385
- };
386
- }
387
296
  // Update search queries for active selectors
388
297
  updateSearchQueriesForActiveSelectors(inputText, cursorPosition) {
389
298
  if (this.showFileSelector && this.atPosition >= 0) {
@@ -411,9 +320,6 @@ export class InputManager {
411
320
  // Only activate command selector if '/' is at the start of input
412
321
  this.activateCommandSelector(this.cursorPosition - 1);
413
322
  }
414
- else if (char === "#" && this.cursorPosition === 1) {
415
- // Memory message detection will be handled in submit
416
- }
417
323
  else {
418
324
  // Update search queries for active selectors
419
325
  this.updateSearchQueriesForActiveSelectors(this.inputText, this.cursorPosition);
@@ -539,12 +445,12 @@ export class InputManager {
539
445
  }
540
446
  }
541
447
  // Task manager state methods
542
- getShowTaskManager() {
543
- return this.showTaskManager;
448
+ getShowBackgroundTaskManager() {
449
+ return this.showBackgroundTaskManager;
544
450
  }
545
- setShowTaskManager(show) {
546
- this.showTaskManager = show;
547
- this.callbacks.onTaskManagerStateChange?.(show);
451
+ setShowBackgroundTaskManager(show) {
452
+ this.showBackgroundTaskManager = show;
453
+ this.callbacks.onBackgroundTaskManagerStateChange?.(show);
548
454
  }
549
455
  getShowMcpManager() {
550
456
  return this.showMcpManager;
@@ -560,6 +466,13 @@ export class InputManager {
560
466
  this.showRewindManager = show;
561
467
  this.callbacks.onRewindManagerStateChange?.(show);
562
468
  }
469
+ getShowHelp() {
470
+ return this.showHelp;
471
+ }
472
+ setShowHelp(show) {
473
+ this.showHelp = show;
474
+ this.callbacks.onHelpStateChange?.(show);
475
+ }
563
476
  // Permission mode methods
564
477
  getPermissionMode() {
565
478
  return this.permissionMode;
@@ -586,13 +499,6 @@ export class InputManager {
586
499
  return;
587
500
  }
588
501
  if (this.inputText.trim()) {
589
- const trimmedInput = this.inputText.trim();
590
- // Check if it's a memory message (starts with # and only one line)
591
- if (trimmedInput.startsWith("#") && !trimmedInput.includes("\n")) {
592
- // Activate memory type selector
593
- this.activateMemoryTypeSelector(trimmedInput);
594
- return;
595
- }
596
502
  // Extract image information
597
503
  const imageRegex = /\[Image #(\d+)\]/g;
598
504
  const matches = [...this.inputText.matchAll(imageRegex)];
@@ -714,14 +620,6 @@ export class InputManager {
714
620
  this.moveCursorRight();
715
621
  return true;
716
622
  }
717
- if (("home" in key && key.home) || (key.ctrl && input === "a")) {
718
- this.moveCursorToStart();
719
- return true;
720
- }
721
- if (("end" in key && key.end) || (key.ctrl && input === "e")) {
722
- this.moveCursorToEnd();
723
- return true;
724
- }
725
623
  // Handle Ctrl+V for pasting images
726
624
  if (key.ctrl && input === "v") {
727
625
  this.handlePasteImage().catch((error) => {
@@ -739,15 +637,6 @@ export class InputManager {
739
637
  this.callbacks.onBackgroundCurrentTask?.();
740
638
  return true;
741
639
  }
742
- // Handle up/down keys for history navigation (only when no selector is active)
743
- if (key.upArrow && !this.showFileSelector && !this.showCommandSelector) {
744
- this.navigateHistory("up", this.inputText);
745
- return true;
746
- }
747
- if (key.downArrow && !this.showFileSelector && !this.showCommandSelector) {
748
- this.navigateHistory("down", this.inputText);
749
- return true;
750
- }
751
640
  // Handle typing input
752
641
  if (input &&
753
642
  !key.ctrl &&
@@ -773,7 +662,11 @@ export class InputManager {
773
662
  return true;
774
663
  }
775
664
  // Handle interrupt request - use Esc key to interrupt AI request or command
776
- if (key.escape && (isLoading || isCommandRunning)) {
665
+ if (key.escape &&
666
+ (isLoading || isCommandRunning) &&
667
+ !this.showBackgroundTaskManager &&
668
+ !this.showMcpManager &&
669
+ !this.showRewindManager) {
777
670
  // Unified interrupt for AI message generation and command execution
778
671
  this.callbacks.onAbortMessage?.();
779
672
  return true;
@@ -788,16 +681,17 @@ export class InputManager {
788
681
  if (this.showFileSelector ||
789
682
  this.showCommandSelector ||
790
683
  this.showHistorySearch ||
791
- this.showMemoryTypeSelector ||
792
- this.showTaskManager ||
684
+ this.showBackgroundTaskManager ||
793
685
  this.showMcpManager ||
794
- this.showRewindManager) {
795
- if (this.showMemoryTypeSelector ||
796
- this.showTaskManager ||
686
+ this.showRewindManager ||
687
+ this.showHelp) {
688
+ if (this.showBackgroundTaskManager ||
797
689
  this.showMcpManager ||
798
- this.showRewindManager) {
799
- // Memory type selector, task manager, MCP manager and Rewind don't need to handle input, handled by component itself
800
- return false;
690
+ this.showRewindManager ||
691
+ this.showHelp) {
692
+ // Task manager, MCP manager, Rewind and Help don't need to handle input, handled by component itself
693
+ // Return true to indicate we've "handled" it (by ignoring it) so it doesn't leak to normal input
694
+ return true;
801
695
  }
802
696
  if (this.showHistorySearch) {
803
697
  if (key.escape) {
@@ -1 +1 @@
1
- {"version":3,"file":"print-cli.d.ts","sourceRoot":"","sources":["../src/print-cli.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAgBD,wBAAsB,aAAa,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAgL3E"}
1
+ {"version":3,"file":"print-cli.d.ts","sourceRoot":"","sources":["../src/print-cli.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAgBD,wBAAsB,aAAa,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA8J3E"}
package/dist/print-cli.js CHANGED
@@ -48,38 +48,10 @@ export async function startPrintCli(options) {
48
48
  // FR-001: Stream content updates for real-time display - output only the new chunk
49
49
  process.stdout.write(chunk);
50
50
  },
51
- // Tool block callback - display tool name when tool starts
52
- onToolBlockUpdated: (params) => {
53
- // Print tool name only during 'running' stage (happens once per tool call)
54
- if (params.stage === "running" && params.name) {
55
- process.stdout.write(`\n🔧 ${params.name}`);
56
- if (params.compactParams) {
57
- process.stdout.write(` ${params.compactParams}`);
58
- }
59
- process.stdout.write(`\n`);
60
- }
61
- },
62
- // Subagent block callbacks
63
- onSubAgentBlockAdded: (subagentId, parameters) => {
64
- // Display subagent creation with indentation
65
- process.stdout.write(`\n🤖 Subagent [${parameters.subagent_type}]: ${parameters.description}\n`);
66
- },
67
- onSubAgentBlockUpdated: (subagentId, status) => {
68
- // Display subagent status updates
69
- const statusIconMap = {
70
- active: "🔄",
71
- completed: "✅",
72
- error: "❌",
73
- aborted: "⚠️",
74
- };
75
- const statusIcon = statusIconMap[status] ?? "🔄";
76
- process.stdout.write(` ${statusIcon} Subagent status: ${status}\n`);
77
- },
78
51
  // Subagent message callbacks
79
52
  onSubagentAssistantMessageAdded: (subagentId) => {
80
53
  subagentReasoningStates.set(subagentId, false);
81
54
  subagentContentStates.set(subagentId, false);
82
- // Subagent assistant message started - add indentation
83
55
  process.stdout.write("\n ");
84
56
  },
85
57
  onSubagentAssistantReasoningUpdated: (subagentId, chunk) => {
@@ -95,13 +67,22 @@ export async function startPrintCli(options) {
95
67
  process.stdout.write("\n 📝 Response: ");
96
68
  subagentContentStates.set(subagentId, true);
97
69
  }
98
- // Stream subagent content with indentation - output only the new chunk
99
70
  process.stdout.write(chunk);
100
71
  },
101
72
  onSubagentUserMessageAdded: (_subagentId, params) => {
102
- // Display subagent user messages with indentation
103
73
  process.stdout.write(`\n 👤 User: ${params.content}\n`);
104
74
  },
75
+ // Tool block callback - display tool name when tool starts
76
+ onToolBlockUpdated: (params) => {
77
+ // Print tool name only during 'running' stage (happens once per tool call)
78
+ if (params.stage === "running" && params.name) {
79
+ process.stdout.write(`\n🔧 ${params.name}`);
80
+ if (params.compactParams) {
81
+ process.stdout.write(` ${params.compactParams}`);
82
+ }
83
+ process.stdout.write(`\n`);
84
+ }
85
+ },
105
86
  // Error block callback
106
87
  onErrorBlockAdded: (error) => {
107
88
  // Display error blocks with distinct formatting
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-code",
3
- "version": "0.5.0",
3
+ "version": "0.6.2",
4
4
  "description": "CLI-based code assistant powered by AI, built with React and Ink",
5
5
  "repository": {
6
6
  "type": "git",
@@ -30,15 +30,14 @@
30
30
  ],
31
31
  "dependencies": {
32
32
  "chalk": "^5.6.2",
33
- "cli-highlight": "^2.1.11",
34
33
  "diff": "^8.0.2",
35
34
  "glob": "^13.0.0",
36
- "ink": "^6.5.1",
37
- "marked": "^11.2.0",
35
+ "ink": "^6.7.0",
36
+ "marked": "^17.0.2",
38
37
  "react": "^19.2.4",
39
38
  "react-dom": "19.2.4",
40
39
  "yargs": "^17.7.2",
41
- "wave-agent-sdk": "0.5.0"
40
+ "wave-agent-sdk": "0.6.2"
42
41
  },
43
42
  "devDependencies": {
44
43
  "@types/react": "^19.1.8",
@@ -60,7 +59,7 @@
60
59
  },
61
60
  "license": "MIT",
62
61
  "scripts": {
63
- "wave": "tsx src/index.ts",
62
+ "wave": "tsx --tsconfig tsconfig.dev.json src/index.ts",
64
63
  "build": "rimraf dist && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
65
64
  "type-check": "tsc --noEmit --incremental",
66
65
  "watch": "tsc -p tsconfig.build.json --watch & tsc-alias -p tsconfig.build.json --watch",
@@ -1,6 +1,7 @@
1
- import React from "react";
1
+ import React, { useState, useEffect, useRef } from "react";
2
+ import { useStdout } from "ink";
2
3
  import { ChatInterface } from "./ChatInterface.js";
3
- import { ChatProvider } from "../contexts/useChat.js";
4
+ import { ChatProvider, useChat } from "../contexts/useChat.js";
4
5
  import { AppProvider } from "../contexts/useAppConfig.js";
5
6
 
6
7
  interface AppProps {
@@ -16,11 +17,58 @@ const AppWithProviders: React.FC<{
16
17
  }> = ({ bypassPermissions, pluginDirs }) => {
17
18
  return (
18
19
  <ChatProvider bypassPermissions={bypassPermissions} pluginDirs={pluginDirs}>
19
- <ChatInterface />
20
+ <ChatInterfaceWithRemount />
20
21
  </ChatProvider>
21
22
  );
22
23
  };
23
24
 
25
+ const ChatInterfaceWithRemount: React.FC = () => {
26
+ const { stdout } = useStdout();
27
+ const { isExpanded, rewindId, wasLastDetailsTooTall, sessionId } = useChat();
28
+
29
+ const [remountKey, setRemountKey] = useState(
30
+ String(isExpanded) + rewindId + wasLastDetailsTooTall,
31
+ );
32
+
33
+ const prevSessionId = useRef(sessionId);
34
+
35
+ useEffect(() => {
36
+ const newKey =
37
+ String(isExpanded) +
38
+ rewindId +
39
+ wasLastDetailsTooTall +
40
+ (prevSessionId.current && sessionId && prevSessionId.current !== sessionId
41
+ ? sessionId
42
+ : "");
43
+
44
+ if (newKey !== remountKey) {
45
+ const timeout = setTimeout(() => {
46
+ stdout?.write("\u001b[2J\u001b[0;0H", (err?: Error | null) => {
47
+ if (err) {
48
+ console.error("Failed to clear terminal:", err);
49
+ }
50
+ setRemountKey(newKey);
51
+ });
52
+ }, 100);
53
+
54
+ return () => clearTimeout(timeout);
55
+ }
56
+
57
+ if (sessionId) {
58
+ prevSessionId.current = sessionId;
59
+ }
60
+ }, [
61
+ isExpanded,
62
+ rewindId,
63
+ wasLastDetailsTooTall,
64
+ sessionId,
65
+ remountKey,
66
+ stdout,
67
+ ]);
68
+
69
+ return <ChatInterface key={remountKey} />;
70
+ };
71
+
24
72
  export const App: React.FC<AppProps> = ({
25
73
  restoreSessionId,
26
74
  continueLastSession,
@@ -12,11 +12,13 @@ interface Task {
12
12
  runtime?: number;
13
13
  }
14
14
 
15
- export interface TaskManagerProps {
15
+ export interface BackgroundTaskManagerProps {
16
16
  onCancel: () => void;
17
17
  }
18
18
 
19
- export const TaskManager: React.FC<TaskManagerProps> = ({ onCancel }) => {
19
+ export const BackgroundTaskManager: React.FC<BackgroundTaskManagerProps> = ({
20
+ onCancel,
21
+ }) => {
20
22
  const { backgroundTasks, getBackgroundTaskOutput, stopBackgroundTask } =
21
23
  useChat();
22
24
  const [tasks, setTasks] = useState<Task[]>([]);
@@ -1,20 +1,26 @@
1
- import React from "react";
2
- import { Box } from "ink";
1
+ import React, { useState, useCallback } from "react";
2
+ import { Box, useStdout } from "ink";
3
3
  import { MessageList } from "./MessageList.js";
4
4
  import { InputBox } from "./InputBox.js";
5
- import { Confirmation } from "./Confirmation.js";
5
+ import { LoadingIndicator } from "./LoadingIndicator.js";
6
+ import { TaskList } from "./TaskList.js";
7
+ import { ConfirmationDetails } from "./ConfirmationDetails.js";
8
+ import { ConfirmationSelector } from "./ConfirmationSelector.js";
9
+
6
10
  import { useChat } from "../contexts/useChat.js";
11
+ import type { PermissionDecision } from "wave-agent-sdk";
7
12
 
8
13
  export const ChatInterface: React.FC = () => {
14
+ const { stdout } = useStdout();
15
+ const [isDetailsTooTall, setIsDetailsTooTall] = useState(false);
16
+
9
17
  const {
10
18
  messages,
11
19
  isLoading,
12
20
  isCommandRunning,
13
- userInputHistory,
14
21
  isCompressing,
15
22
  sendMessage,
16
23
  abortMessage,
17
- saveMemory,
18
24
  mcpServers,
19
25
  connectMcpServer,
20
26
  disconnectMcpServer,
@@ -26,45 +32,96 @@ export const ChatInterface: React.FC = () => {
26
32
  isConfirmationVisible,
27
33
  confirmingTool,
28
34
  handleConfirmationDecision,
29
- handleConfirmationCancel,
30
- rewindId,
35
+ handleConfirmationCancel: originalHandleConfirmationCancel,
36
+ setWasLastDetailsTooTall,
31
37
  } = useChat();
32
38
 
39
+ const handleHeightMeasured = useCallback(
40
+ (height: number) => {
41
+ const terminalHeight = stdout?.rows || 24;
42
+ if (height > terminalHeight - 10) {
43
+ setIsDetailsTooTall(true);
44
+ } else {
45
+ setIsDetailsTooTall(false);
46
+ }
47
+ },
48
+ [stdout?.rows],
49
+ );
50
+
51
+ const handleConfirmationCancel = useCallback(() => {
52
+ if (isDetailsTooTall) {
53
+ setWasLastDetailsTooTall((prev) => prev + 1);
54
+ setIsDetailsTooTall(false);
55
+ }
56
+ originalHandleConfirmationCancel();
57
+ }, [
58
+ isDetailsTooTall,
59
+ originalHandleConfirmationCancel,
60
+ setWasLastDetailsTooTall,
61
+ ]);
62
+
63
+ const wrappedHandleConfirmationDecision = useCallback(
64
+ (decision: PermissionDecision) => {
65
+ if (isDetailsTooTall) {
66
+ setWasLastDetailsTooTall((prev) => prev + 1);
67
+ setIsDetailsTooTall(false);
68
+ }
69
+ handleConfirmationDecision(decision);
70
+ },
71
+ [isDetailsTooTall, handleConfirmationDecision, setWasLastDetailsTooTall],
72
+ );
73
+
33
74
  if (!sessionId) return null;
34
75
 
35
76
  return (
36
- <Box flexDirection="column" height="100%" paddingY={1} paddingRight={1}>
77
+ <Box flexDirection="column">
37
78
  <MessageList
38
79
  messages={messages}
39
80
  isLoading={isLoading}
40
81
  isCommandRunning={isCommandRunning}
41
- isCompressing={isCompressing}
42
- latestTotalTokens={latestTotalTokens}
43
82
  isExpanded={isExpanded}
44
- key={String(isExpanded) + sessionId + rewindId}
83
+ forceStaticLastMessage={isDetailsTooTall}
45
84
  />
46
85
 
86
+ {(isLoading || isCommandRunning || isCompressing) &&
87
+ !isConfirmationVisible &&
88
+ !isExpanded && (
89
+ <LoadingIndicator
90
+ isLoading={isLoading}
91
+ isCommandRunning={isCommandRunning}
92
+ isCompressing={isCompressing}
93
+ latestTotalTokens={latestTotalTokens}
94
+ />
95
+ )}
96
+ {!isConfirmationVisible && !isExpanded && <TaskList />}
97
+
47
98
  {isConfirmationVisible && (
48
- <Confirmation
49
- toolName={confirmingTool!.name}
50
- toolInput={confirmingTool!.input}
51
- suggestedPrefix={confirmingTool!.suggestedPrefix}
52
- hidePersistentOption={confirmingTool!.hidePersistentOption}
53
- isExpanded={isExpanded}
54
- onDecision={handleConfirmationDecision}
55
- onCancel={handleConfirmationCancel}
56
- onAbort={abortMessage}
57
- />
99
+ <>
100
+ <ConfirmationDetails
101
+ toolName={confirmingTool!.name}
102
+ toolInput={confirmingTool!.input}
103
+ isExpanded={isExpanded}
104
+ onHeightMeasured={handleHeightMeasured}
105
+ />
106
+ <ConfirmationSelector
107
+ toolName={confirmingTool!.name}
108
+ toolInput={confirmingTool!.input}
109
+ suggestedPrefix={confirmingTool!.suggestedPrefix}
110
+ hidePersistentOption={confirmingTool!.hidePersistentOption}
111
+ isExpanded={isExpanded}
112
+ onDecision={wrappedHandleConfirmationDecision}
113
+ onCancel={handleConfirmationCancel}
114
+ onAbort={abortMessage}
115
+ />
116
+ </>
58
117
  )}
59
118
 
60
119
  {!isConfirmationVisible && !isExpanded && (
61
120
  <InputBox
62
121
  isLoading={isLoading}
63
122
  isCommandRunning={isCommandRunning}
64
- userInputHistory={userInputHistory}
65
123
  sendMessage={sendMessage}
66
124
  abortMessage={abortMessage}
67
- saveMemory={saveMemory}
68
125
  mcpServers={mcpServers}
69
126
  connectMcpServer={connectMcpServer}
70
127
  disconnectMcpServer={disconnectMcpServer}