wave-code 1.0.10 → 1.1.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 (248) hide show
  1. package/bin/wave-code.js +8 -2
  2. package/dist/bundle/wave.mjs +3231 -0
  3. package/package.json +24 -25
  4. package/src/cli.tsx +6 -1
  5. package/src/commands/update.ts +2 -7
  6. package/src/components/App.tsx +3 -7
  7. package/src/components/BtwDisplay.tsx +10 -1
  8. package/src/components/ChatInterface.tsx +21 -52
  9. package/src/components/ConfirmationDetails.tsx +144 -37
  10. package/src/components/DiffDisplay.tsx +335 -301
  11. package/src/components/InputBox.tsx +17 -2
  12. package/src/components/LoadingIndicator.tsx +16 -2
  13. package/src/components/MessageBlockItem.tsx +2 -4
  14. package/src/components/MessageList.tsx +75 -14
  15. package/src/components/ReasoningDisplay.tsx +2 -4
  16. package/src/components/SessionSelector.tsx +82 -11
  17. package/src/components/SkillsManager.tsx +312 -0
  18. package/src/components/StatusCommand.tsx +2 -9
  19. package/src/components/ToolDisplay.tsx +2 -4
  20. package/src/constants/commands.ts +6 -0
  21. package/src/contexts/useChat.tsx +245 -301
  22. package/src/daemon/commands.ts +57 -27
  23. package/src/daemon/jsonRpcClient.ts +1 -1
  24. package/src/daemon/socketClient.ts +2 -2
  25. package/src/hooks/useInputManager.ts +7 -1
  26. package/src/index.ts +57 -10
  27. package/src/managers/inputHandlers.ts +2 -0
  28. package/src/managers/inputReducer.ts +195 -104
  29. package/src/print-cli.ts +25 -17
  30. package/src/reducers/selectorReducer.ts +10 -0
  31. package/src/reducers/skillsManagerReducer.ts +91 -0
  32. package/src/session-selector-cli.tsx +269 -28
  33. package/src/stdio/agentBridge.ts +30 -21
  34. package/src/stdio/daemonServer.ts +3 -1
  35. package/src/stdio/protocol.ts +2 -0
  36. package/src/utils/clipboard.ts +21 -0
  37. package/src/utils/highlightUtils.ts +46 -1
  38. package/src/utils/readPackageJson.ts +29 -0
  39. package/src/utils/streamingText.ts +11 -0
  40. package/src/utils/worktree.ts +191 -43
  41. package/dist/cli.d.ts +0 -6
  42. package/dist/cli.js +0 -53
  43. package/dist/commands/plugin/disable.d.ts +0 -5
  44. package/dist/commands/plugin/disable.js +0 -15
  45. package/dist/commands/plugin/enable.d.ts +0 -5
  46. package/dist/commands/plugin/enable.js +0 -15
  47. package/dist/commands/plugin/install.d.ts +0 -5
  48. package/dist/commands/plugin/install.js +0 -21
  49. package/dist/commands/plugin/list.d.ts +0 -1
  50. package/dist/commands/plugin/list.js +0 -31
  51. package/dist/commands/plugin/marketplace.d.ts +0 -10
  52. package/dist/commands/plugin/marketplace.js +0 -90
  53. package/dist/commands/plugin/uninstall.d.ts +0 -3
  54. package/dist/commands/plugin/uninstall.js +0 -16
  55. package/dist/commands/plugin/update.d.ts +0 -3
  56. package/dist/commands/plugin/update.js +0 -15
  57. package/dist/commands/update.d.ts +0 -1
  58. package/dist/commands/update.js +0 -97
  59. package/dist/components/AgentsManager.d.ts +0 -7
  60. package/dist/components/AgentsManager.js +0 -109
  61. package/dist/components/App.d.ts +0 -9
  62. package/dist/components/App.js +0 -76
  63. package/dist/components/BackgroundTaskManager.d.ts +0 -5
  64. package/dist/components/BackgroundTaskManager.js +0 -99
  65. package/dist/components/BangDisplay.d.ts +0 -8
  66. package/dist/components/BangDisplay.js +0 -17
  67. package/dist/components/BtwDisplay.d.ts +0 -7
  68. package/dist/components/BtwDisplay.js +0 -19
  69. package/dist/components/ChatInterface.d.ts +0 -2
  70. package/dist/components/ChatInterface.js +0 -61
  71. package/dist/components/CommandSelector.d.ts +0 -10
  72. package/dist/components/CommandSelector.js +0 -66
  73. package/dist/components/CompactDisplay.d.ts +0 -8
  74. package/dist/components/CompactDisplay.js +0 -13
  75. package/dist/components/ConfirmationDetails.d.ts +0 -9
  76. package/dist/components/ConfirmationDetails.js +0 -45
  77. package/dist/components/ConfirmationSelector.d.ts +0 -13
  78. package/dist/components/ConfirmationSelector.js +0 -127
  79. package/dist/components/DiffDisplay.d.ts +0 -8
  80. package/dist/components/DiffDisplay.js +0 -201
  81. package/dist/components/DiscoverView.d.ts +0 -2
  82. package/dist/components/DiscoverView.js +0 -37
  83. package/dist/components/FileSelector.d.ts +0 -11
  84. package/dist/components/FileSelector.js +0 -50
  85. package/dist/components/HelpView.d.ts +0 -7
  86. package/dist/components/HelpView.js +0 -74
  87. package/dist/components/HistorySearch.d.ts +0 -8
  88. package/dist/components/HistorySearch.js +0 -66
  89. package/dist/components/InputBox.d.ts +0 -24
  90. package/dist/components/InputBox.js +0 -149
  91. package/dist/components/InstalledView.d.ts +0 -2
  92. package/dist/components/InstalledView.js +0 -45
  93. package/dist/components/LoadingIndicator.d.ts +0 -10
  94. package/dist/components/LoadingIndicator.js +0 -6
  95. package/dist/components/LoginCommand.d.ts +0 -5
  96. package/dist/components/LoginCommand.js +0 -153
  97. package/dist/components/Markdown.d.ts +0 -5
  98. package/dist/components/Markdown.js +0 -127
  99. package/dist/components/MarketplaceAddForm.d.ts +0 -2
  100. package/dist/components/MarketplaceAddForm.js +0 -55
  101. package/dist/components/MarketplaceDetail.d.ts +0 -2
  102. package/dist/components/MarketplaceDetail.js +0 -74
  103. package/dist/components/MarketplaceList.d.ts +0 -8
  104. package/dist/components/MarketplaceList.js +0 -17
  105. package/dist/components/MarketplaceView.d.ts +0 -2
  106. package/dist/components/MarketplaceView.js +0 -41
  107. package/dist/components/McpManager.d.ts +0 -9
  108. package/dist/components/McpManager.js +0 -88
  109. package/dist/components/MessageBlockItem.d.ts +0 -8
  110. package/dist/components/MessageBlockItem.js +0 -15
  111. package/dist/components/MessageList.d.ts +0 -10
  112. package/dist/components/MessageList.js +0 -80
  113. package/dist/components/ModelSelector.d.ts +0 -8
  114. package/dist/components/ModelSelector.js +0 -45
  115. package/dist/components/Notifications.d.ts +0 -7
  116. package/dist/components/Notifications.js +0 -9
  117. package/dist/components/PlanDisplay.d.ts +0 -7
  118. package/dist/components/PlanDisplay.js +0 -6
  119. package/dist/components/PluginDetail.d.ts +0 -2
  120. package/dist/components/PluginDetail.js +0 -87
  121. package/dist/components/PluginList.d.ts +0 -13
  122. package/dist/components/PluginList.js +0 -15
  123. package/dist/components/PluginManagerShell.d.ts +0 -6
  124. package/dist/components/PluginManagerShell.js +0 -92
  125. package/dist/components/PluginManagerTypes.d.ts +0 -35
  126. package/dist/components/PluginManagerTypes.js +0 -1
  127. package/dist/components/QueuedMessageList.d.ts +0 -2
  128. package/dist/components/QueuedMessageList.js +0 -18
  129. package/dist/components/ReasoningDisplay.d.ts +0 -8
  130. package/dist/components/ReasoningDisplay.js +0 -13
  131. package/dist/components/RewindCommand.d.ts +0 -12
  132. package/dist/components/RewindCommand.js +0 -78
  133. package/dist/components/SessionSelector.d.ts +0 -12
  134. package/dist/components/SessionSelector.js +0 -47
  135. package/dist/components/StatusCommand.d.ts +0 -5
  136. package/dist/components/StatusCommand.js +0 -28
  137. package/dist/components/StatusLine.d.ts +0 -6
  138. package/dist/components/StatusLine.js +0 -9
  139. package/dist/components/TaskList.d.ts +0 -5
  140. package/dist/components/TaskList.js +0 -197
  141. package/dist/components/TaskNotificationMessage.d.ts +0 -5
  142. package/dist/components/TaskNotificationMessage.js +0 -12
  143. package/dist/components/ToolDisplay.d.ts +0 -9
  144. package/dist/components/ToolDisplay.js +0 -55
  145. package/dist/components/WorkflowManager.d.ts +0 -5
  146. package/dist/components/WorkflowManager.js +0 -102
  147. package/dist/components/WorktreeExitPrompt.d.ts +0 -12
  148. package/dist/components/WorktreeExitPrompt.js +0 -35
  149. package/dist/constants/commands.d.ts +0 -2
  150. package/dist/constants/commands.js +0 -92
  151. package/dist/contexts/PluginManagerContext.d.ts +0 -3
  152. package/dist/contexts/PluginManagerContext.js +0 -9
  153. package/dist/contexts/useAppConfig.d.ts +0 -10
  154. package/dist/contexts/useAppConfig.js +0 -13
  155. package/dist/contexts/useChat.d.ts +0 -101
  156. package/dist/contexts/useChat.js +0 -993
  157. package/dist/contracts/status.d.ts +0 -7
  158. package/dist/contracts/status.js +0 -1
  159. package/dist/daemon/commands.d.ts +0 -49
  160. package/dist/daemon/commands.js +0 -341
  161. package/dist/daemon/jsonRpcClient.d.ts +0 -38
  162. package/dist/daemon/jsonRpcClient.js +0 -129
  163. package/dist/daemon/socketClient.d.ts +0 -13
  164. package/dist/daemon/socketClient.js +0 -26
  165. package/dist/daemon-cli.d.ts +0 -10
  166. package/dist/daemon-cli.js +0 -15
  167. package/dist/hooks/useInputManager.d.ts +0 -75
  168. package/dist/hooks/useInputManager.js +0 -583
  169. package/dist/hooks/usePluginManager.d.ts +0 -4
  170. package/dist/hooks/usePluginManager.js +0 -265
  171. package/dist/hooks/useTasks.d.ts +0 -1
  172. package/dist/hooks/useTasks.js +0 -5
  173. package/dist/index.d.ts +0 -4
  174. package/dist/index.js +0 -447
  175. package/dist/managers/inputHandlers.d.ts +0 -30
  176. package/dist/managers/inputHandlers.js +0 -625
  177. package/dist/managers/inputReducer.d.ts +0 -271
  178. package/dist/managers/inputReducer.js +0 -1121
  179. package/dist/plugin-manager-cli.d.ts +0 -5
  180. package/dist/plugin-manager-cli.js +0 -12
  181. package/dist/print-cli.d.ts +0 -10
  182. package/dist/print-cli.js +0 -209
  183. package/dist/reducers/agentsManagerReducer.d.ts +0 -26
  184. package/dist/reducers/agentsManagerReducer.js +0 -54
  185. package/dist/reducers/backgroundTaskManagerReducer.d.ts +0 -62
  186. package/dist/reducers/backgroundTaskManagerReducer.js +0 -106
  187. package/dist/reducers/confirmationReducer.d.ts +0 -37
  188. package/dist/reducers/confirmationReducer.js +0 -193
  189. package/dist/reducers/helpSelectorReducer.d.ts +0 -25
  190. package/dist/reducers/helpSelectorReducer.js +0 -52
  191. package/dist/reducers/marketplaceAddFormReducer.d.ts +0 -24
  192. package/dist/reducers/marketplaceAddFormReducer.js +0 -58
  193. package/dist/reducers/mcpManagerReducer.d.ts +0 -37
  194. package/dist/reducers/mcpManagerReducer.js +0 -74
  195. package/dist/reducers/pluginDetailReducer.d.ts +0 -32
  196. package/dist/reducers/pluginDetailReducer.js +0 -70
  197. package/dist/reducers/questionReducer.d.ts +0 -82
  198. package/dist/reducers/questionReducer.js +0 -375
  199. package/dist/reducers/rewindSelectorReducer.d.ts +0 -21
  200. package/dist/reducers/rewindSelectorReducer.js +0 -37
  201. package/dist/reducers/selectorReducer.d.ts +0 -23
  202. package/dist/reducers/selectorReducer.js +0 -42
  203. package/dist/reducers/workflowManagerReducer.d.ts +0 -40
  204. package/dist/reducers/workflowManagerReducer.js +0 -100
  205. package/dist/session-selector-cli.d.ts +0 -3
  206. package/dist/session-selector-cli.js +0 -25
  207. package/dist/stdio/agentBridge.d.ts +0 -139
  208. package/dist/stdio/agentBridge.js +0 -999
  209. package/dist/stdio/daemonServer.d.ts +0 -67
  210. package/dist/stdio/daemonServer.js +0 -191
  211. package/dist/stdio/index.d.ts +0 -5
  212. package/dist/stdio/index.js +0 -5
  213. package/dist/stdio/jsonRpcConnection.d.ts +0 -39
  214. package/dist/stdio/jsonRpcConnection.js +0 -135
  215. package/dist/stdio/protocol.d.ts +0 -41
  216. package/dist/stdio/protocol.js +0 -26
  217. package/dist/stdio/stdioServer.d.ts +0 -36
  218. package/dist/stdio/stdioServer.js +0 -51
  219. package/dist/stdio-cli.d.ts +0 -8
  220. package/dist/stdio-cli.js +0 -15
  221. package/dist/types.d.ts +0 -20
  222. package/dist/types.js +0 -1
  223. package/dist/utils/bracketedPaste.d.ts +0 -39
  224. package/dist/utils/bracketedPaste.js +0 -122
  225. package/dist/utils/clipboard.d.ts +0 -21
  226. package/dist/utils/clipboard.js +0 -347
  227. package/dist/utils/constants.d.ts +0 -16
  228. package/dist/utils/constants.js +0 -18
  229. package/dist/utils/highlightUtils.d.ts +0 -1
  230. package/dist/utils/highlightUtils.js +0 -93
  231. package/dist/utils/inputUtils.d.ts +0 -17
  232. package/dist/utils/inputUtils.js +0 -67
  233. package/dist/utils/logger.d.ts +0 -71
  234. package/dist/utils/logger.js +0 -259
  235. package/dist/utils/markdownTable.d.ts +0 -34
  236. package/dist/utils/markdownTable.js +0 -302
  237. package/dist/utils/rewindCheckpoints.d.ts +0 -8
  238. package/dist/utils/rewindCheckpoints.js +0 -17
  239. package/dist/utils/throttle.d.ts +0 -14
  240. package/dist/utils/throttle.js +0 -55
  241. package/dist/utils/toolParameterTransforms.d.ts +0 -18
  242. package/dist/utils/toolParameterTransforms.js +0 -73
  243. package/dist/utils/usageSummary.d.ts +0 -28
  244. package/dist/utils/usageSummary.js +0 -121
  245. package/dist/utils/version.d.ts +0 -1
  246. package/dist/utils/version.js +0 -4
  247. package/dist/utils/worktree.d.ts +0 -35
  248. package/dist/utils/worktree.js +0 -219
@@ -7,6 +7,7 @@ import { HistorySearch } from "./HistorySearch.js";
7
7
  import { BackgroundTaskManager } from "./BackgroundTaskManager.js";
8
8
  import { McpManager } from "./McpManager.js";
9
9
  import { AgentsManager } from "./AgentsManager.js";
10
+ import { SkillsManager } from "./SkillsManager.js";
10
11
  import { RewindCommand } from "./RewindCommand.js";
11
12
  import { HelpView } from "./HelpView.js";
12
13
  import { StatusCommand } from "./StatusCommand.js";
@@ -91,6 +92,7 @@ export const InputBox: React.FC<InputBoxProps> = ({
91
92
  queuedMessages,
92
93
  setIsBtwActive,
93
94
  agentDefinitions,
95
+ skills,
94
96
  } = useChat();
95
97
 
96
98
  // Ref to hold setInputText so queue callbacks can access it before useInputManager returns
@@ -143,6 +145,7 @@ export const InputBox: React.FC<InputBoxProps> = ({
143
145
  showPluginManager,
144
146
  showModelSelector,
145
147
  showWorkflowManager,
148
+ showSkillsManager,
146
149
  setShowBackgroundTaskManager,
147
150
  setShowMcpManager,
148
151
  setShowAgentsManager,
@@ -153,6 +156,7 @@ export const InputBox: React.FC<InputBoxProps> = ({
153
156
  setShowPluginManager,
154
157
  setShowModelSelector,
155
158
  setShowWorkflowManager,
159
+ setShowSkillsManager,
156
160
  // Permission mode
157
161
  permissionMode,
158
162
  setPermissionMode,
@@ -216,7 +220,8 @@ export const InputBox: React.FC<InputBoxProps> = ({
216
220
  showBackgroundTaskManager ||
217
221
  showMcpManager ||
218
222
  showAgentsManager ||
219
- showWorkflowManager
223
+ showWorkflowManager ||
224
+ showSkillsManager
220
225
  ) {
221
226
  return;
222
227
  }
@@ -312,6 +317,13 @@ export const InputBox: React.FC<InputBoxProps> = ({
312
317
  />
313
318
  )}
314
319
 
320
+ {showSkillsManager && (
321
+ <SkillsManager
322
+ onCancel={() => setShowSkillsManager(false)}
323
+ skills={skills}
324
+ />
325
+ )}
326
+
315
327
  {showWorkflowManager && (
316
328
  <WorkflowManager onCancel={() => setShowWorkflowManager(false)} />
317
329
  )}
@@ -361,6 +373,7 @@ export const InputBox: React.FC<InputBoxProps> = ({
361
373
  : showBackgroundTaskManager ||
362
374
  showMcpManager ||
363
375
  showAgentsManager ||
376
+ showSkillsManager ||
364
377
  showRewindManager ||
365
378
  showHelp ||
366
379
  showStatusCommand ||
@@ -369,7 +382,9 @@ export const InputBox: React.FC<InputBoxProps> = ({
369
382
  showModelSelector ||
370
383
  showWorkflowManager || (
371
384
  <Box flexDirection="column">
372
- {escClearPending && <Text color="gray">再次按 Esc 清空输入</Text>}
385
+ {escClearPending && (
386
+ <Text color="gray">Press Esc again to clear input</Text>
387
+ )}
373
388
  <Box
374
389
  borderStyle="single"
375
390
  borderColor="gray"
@@ -1,11 +1,16 @@
1
1
  import React from "react";
2
2
  import { Box, Text } from "ink";
3
+ import { streamingTail } from "../utils/streamingText.js";
3
4
 
4
5
  export interface LoadingIndicatorProps {
5
6
  isLoading?: boolean;
6
7
  isCommandRunning?: boolean;
7
8
  isCompacting?: boolean;
8
9
  latestTotalTokens?: number;
10
+ /** Accumulated streaming text from the compaction fork; its last 30
11
+ * characters render after the compacting hint (same tail style as the
12
+ * main-loop streaming blocks). */
13
+ compactionStream?: string;
9
14
  }
10
15
 
11
16
  export const LoadingIndicator = ({
@@ -13,12 +18,13 @@ export const LoadingIndicator = ({
13
18
  isCommandRunning = false,
14
19
  isCompacting = false,
15
20
  latestTotalTokens = 0,
21
+ compactionStream = "",
16
22
  }: LoadingIndicatorProps) => {
17
23
  return (
18
24
  <Box flexDirection="column">
19
25
  {isLoading && !isCompacting && (
20
26
  <Box>
21
- <Text color="yellow">✻ AI is thinking... </Text>
27
+ <Text color="yellow">✻ Thinking </Text>
22
28
  {latestTotalTokens > 0 && (
23
29
  <>
24
30
  <Text color="gray" dimColor>
@@ -47,7 +53,15 @@ export const LoadingIndicator = ({
47
53
  )}
48
54
  {isCommandRunning && <Text color="blue">✻ Command is running...</Text>}
49
55
  {isCompacting && (
50
- <Text color="magenta">✻ Compacting message history...</Text>
56
+ <Box>
57
+ <Text color="magenta">✻ Compacting</Text>
58
+ {compactionStream && (
59
+ <Text color="gray" wrap="truncate-end">
60
+ {" "}
61
+ {streamingTail(compactionStream)}
62
+ </Text>
63
+ )}
64
+ </Box>
51
65
  )}
52
66
  </Box>
53
67
  );
@@ -8,6 +8,7 @@ import { CompactDisplay } from "./CompactDisplay.js";
8
8
  import { ReasoningDisplay } from "./ReasoningDisplay.js";
9
9
  import { Markdown } from "./Markdown.js";
10
10
  import { TaskNotificationMessage } from "./TaskNotificationMessage.js";
11
+ import { streamingTail } from "../utils/streamingText.js";
11
12
 
12
13
  export interface MessageBlockItemProps {
13
14
  block: MessageBlock;
@@ -40,10 +41,7 @@ export const MessageBlockItem = ({
40
41
  </Text>
41
42
  ) : block.stage === "streaming" ? (
42
43
  <Text color="gray" wrap="truncate-end">
43
- {(() => {
44
- const flat = block.content.replace(/\n/g, "\\n");
45
- return flat.length > 30 ? `…${flat.slice(-30)}` : flat;
46
- })()}
44
+ {streamingTail(block.content)}
47
45
  </Text>
48
46
  ) : (
49
47
  <Markdown>{block.content}</Markdown>
@@ -1,6 +1,6 @@
1
- import React from "react";
1
+ import React, { useLayoutEffect, useRef } from "react";
2
2
  import os from "os";
3
- import { Box, Text, Static } from "ink";
3
+ import { Box, Text, Static, useWindowSize } from "ink";
4
4
  import type { Message, MessageBlock } from "wave-agent-sdk";
5
5
  import { MessageBlockItem } from "./MessageBlockItem.js";
6
6
 
@@ -10,22 +10,23 @@ const MAX_MESSAGES_EXPANDED = 10;
10
10
  export interface MessageListProps {
11
11
  messages: Message[];
12
12
  isExpanded?: boolean;
13
- forceStatic?: boolean;
14
13
  version?: string;
15
14
  workdir?: string;
16
15
  }
17
16
 
18
17
  export const MessageList = React.memo(
19
- ({
20
- messages,
21
- isExpanded = false,
22
- forceStatic = false,
23
- version,
24
- workdir,
25
- }: MessageListProps) => {
18
+ ({ messages, isExpanded = false, version, workdir }: MessageListProps) => {
26
19
  const maxMessages = isExpanded
27
20
  ? MAX_MESSAGES_EXPANDED
28
21
  : MAX_MESSAGES_COLLAPSED;
22
+ // Bound the <Static> history to the terminal width. The static node is
23
+ // absolutely positioned, so without an explicit width Yoga measures it
24
+ // against its content: any row whose natural width exceeds the terminal
25
+ // (e.g. long tool compactParams) widens the node past the viewport, and
26
+ // the node's height stays at the pre-resize measurement while children
27
+ // re-layout at the wider width — leaving a block of blank rows between
28
+ // the last message and the input box on session restore.
29
+ const { columns } = useWindowSize();
29
30
 
30
31
  const welcomeMessage = (
31
32
  <Box flexDirection="column" paddingTop={1}>
@@ -81,7 +82,6 @@ export const MessageList = React.memo(
81
82
  (item.block.type === "text" || item.block.type === "reasoning") &&
82
83
  item.block.stage === "end";
83
84
  const isDynamic =
84
- !forceStatic &&
85
85
  !isExpanded &&
86
86
  isInLastMessage &&
87
87
  !isBlockCompleted &&
@@ -103,11 +103,72 @@ export const MessageList = React.memo(
103
103
  ...staticBlocks.map((b) => ({ ...b, isWelcome: false })),
104
104
  ];
105
105
 
106
+ // Track the content that was written into the append-only <Static> zone for
107
+ // each block. Ink's <Static> never updates an already-rendered item, so when
108
+ // a text/reasoning block reopens (stage "end" -> "streaming") the frozen
109
+ // prefix stays on screen while the dynamic zone would re-render the FULL
110
+ // accumulated content — duplicating the first words. Rendering only the
111
+ // delta (content beyond the frozen prefix) in the dynamic zone avoids the
112
+ // overlap.
113
+ const frozenContentRef = useRef(new Map<string, string>());
114
+ // Mirrors <Static>'s internal index (the previous static items.length) so we
115
+ // know which static items are actually appended to the terminal output in
116
+ // this render pass. Only those items get their content frozen.
117
+ const staticIndexRef = useRef(0);
118
+
119
+ useLayoutEffect(() => {
120
+ staticIndexRef.current = staticItems.length;
121
+ }, [staticItems.length]);
122
+
123
+ // Record the content of static items being appended this pass, and prune
124
+ // keys for blocks that no longer exist (e.g. rewound or cleared messages).
125
+ // Only text/reasoning blocks carry content and can reopen (end -> streaming).
126
+ const currentKeys = new Set<string>();
127
+ staticItems.forEach((item, position) => {
128
+ if (item.isWelcome) return;
129
+ currentKeys.add(item.key);
130
+ if (
131
+ position >= staticIndexRef.current &&
132
+ (item.block!.type === "text" || item.block!.type === "reasoning")
133
+ ) {
134
+ frozenContentRef.current.set(item.key, item.block!.content);
135
+ }
136
+ });
137
+ for (const item of dynamicBlocks) {
138
+ currentKeys.add(item.key);
139
+ }
140
+ for (const key of frozenContentRef.current.keys()) {
141
+ if (!currentKeys.has(key)) {
142
+ frozenContentRef.current.delete(key);
143
+ }
144
+ }
145
+
146
+ // Reopened blocks (in the dynamic zone but previously written to static)
147
+ // must render only the content beyond the frozen prefix, otherwise the
148
+ // already-displayed prefix appears twice on screen.
149
+ const dynamicBlocksWithDelta = dynamicBlocks.map((item) => {
150
+ const frozen = frozenContentRef.current.get(item.key);
151
+ if (
152
+ frozen !== undefined &&
153
+ (item.block.type === "text" || item.block.type === "reasoning") &&
154
+ item.block.content.length > frozen.length
155
+ ) {
156
+ return {
157
+ ...item,
158
+ block: {
159
+ ...item.block,
160
+ content: item.block.content.slice(frozen.length),
161
+ },
162
+ };
163
+ }
164
+ return item;
165
+ });
166
+
106
167
  return (
107
168
  <Box flexDirection="column" paddingBottom={1}>
108
169
  {/* Static items (Welcome message + Static blocks) */}
109
170
  {staticItems.length > 0 && (
110
- <Static items={staticItems}>
171
+ <Static items={staticItems} style={{ width: columns }}>
111
172
  {(item) => {
112
173
  if (item.isWelcome) {
113
174
  return (
@@ -136,9 +197,9 @@ export const MessageList = React.memo(
136
197
  )}
137
198
 
138
199
  {/* Dynamic blocks */}
139
- {dynamicBlocks.length > 0 && (
200
+ {dynamicBlocksWithDelta.length > 0 && (
140
201
  <Box flexDirection="column">
141
- {dynamicBlocks.map((item) => (
202
+ {dynamicBlocksWithDelta.map((item) => (
142
203
  <MessageBlockItem
143
204
  key={item.key}
144
205
  block={item.block}
@@ -2,6 +2,7 @@ import React from "react";
2
2
  import { Box, Text } from "ink";
3
3
  import type { ReasoningBlock } from "wave-agent-sdk";
4
4
  import { Markdown } from "./Markdown.js";
5
+ import { streamingTail } from "../utils/streamingText.js";
5
6
 
6
7
  interface ReasoningDisplayProps {
7
8
  block: ReasoningBlock;
@@ -32,10 +33,7 @@ export const ReasoningDisplay: React.FC<ReasoningDisplayProps> = ({
32
33
  <Text color="white">{content}</Text>
33
34
  ) : stage === "streaming" ? (
34
35
  <Text color="gray" wrap="truncate-end">
35
- {` ${(() => {
36
- const flat = content.replace(/\n/g, "\\n");
37
- return flat.length > 30 ? `…${flat.slice(-30)}` : flat;
38
- })()}`}
36
+ {` ${streamingTail(content)}`}
39
37
  </Text>
40
38
  ) : (
41
39
  <Markdown>{content}</Markdown>
@@ -1,4 +1,4 @@
1
- import React, { useReducer, useEffect } from "react";
1
+ import React, { useReducer, useEffect, useRef } from "react";
2
2
  import { Box, Text, useInput } from "ink";
3
3
  import type { SessionMetadata } from "wave-agent-sdk";
4
4
  import {
@@ -12,34 +12,87 @@ export interface SessionSelectorProps {
12
12
  sessions: SessionItem[];
13
13
  onSelect: (sessionId: string) => void;
14
14
  onCancel: () => void;
15
+ /**
16
+ * Same-repo worktree paths (from `git worktree list`). The Ctrl+W
17
+ * all-worktrees toggle is only enabled when more than one worktree exists.
18
+ */
19
+ worktreePaths?: string[];
20
+ /** Show each session's project directory at the end of its row. */
21
+ showProjectPath?: boolean;
22
+ /** Emitted with the new scope when the user toggles all-projects (Ctrl+A). */
23
+ onToggleAllProjects?: (showAllProjects: boolean) => void;
24
+ /** Emitted with the new scope when the user toggles all-worktrees (Ctrl+W). */
25
+ onToggleAllWorktrees?: (showAllWorktrees: boolean) => void;
15
26
  }
16
27
 
17
28
  export const SessionSelector: React.FC<SessionSelectorProps> = ({
18
29
  sessions,
19
30
  onSelect,
20
31
  onCancel,
32
+ worktreePaths = [],
33
+ showProjectPath = false,
34
+ onToggleAllProjects,
35
+ onToggleAllWorktrees,
21
36
  }) => {
22
37
  const [state, dispatch] = useReducer(selectorReducer<SessionItem>, {
23
38
  selectedIndex: 0,
24
39
  pendingDecision: null,
25
40
  items: [],
41
+ showAllProjects: false,
42
+ showAllWorktrees: false,
26
43
  } as SelectorState<SessionItem>);
27
44
 
28
- const { selectedIndex, pendingDecision, items } = state;
45
+ const {
46
+ selectedIndex,
47
+ pendingDecision,
48
+ items,
49
+ showAllProjects,
50
+ showAllWorktrees,
51
+ } = state;
29
52
 
30
53
  // Sync sessions into reducer state
31
54
  useEffect(() => {
32
55
  dispatch({ type: "SET_ITEMS", items: sessions });
33
56
  }, [sessions]);
34
57
 
35
- useInput((_input, key) => {
36
- dispatch({
37
- type: "HANDLE_KEY",
38
- key,
39
- hasInsert: false,
40
- });
58
+ useInput((input, key) => {
59
+ // Ctrl+A — toggle all-projects scope (only when the parent supports it)
60
+ if (key.ctrl && input.toLowerCase() === "a" && onToggleAllProjects) {
61
+ dispatch({ type: "TOGGLE_ALL_PROJECTS" });
62
+ return;
63
+ }
64
+ // Ctrl+W — toggle all-worktrees scope (only with multiple worktrees)
65
+ if (
66
+ key.ctrl &&
67
+ input.toLowerCase() === "w" &&
68
+ onToggleAllWorktrees &&
69
+ worktreePaths.length > 1
70
+ ) {
71
+ dispatch({ type: "TOGGLE_ALL_WORKTREES" });
72
+ return;
73
+ }
74
+ dispatch({ type: "HANDLE_KEY", key, hasInsert: false });
41
75
  });
42
76
 
77
+ // Report scope toggles to the parent so it can reload the session list.
78
+ // Skip the initial render — the parent already starts in the default scope.
79
+ const projectsFirstRender = useRef(true);
80
+ useEffect(() => {
81
+ if (projectsFirstRender.current) {
82
+ projectsFirstRender.current = false;
83
+ return;
84
+ }
85
+ onToggleAllProjects?.(showAllProjects ?? false);
86
+ }, [showAllProjects, onToggleAllProjects]);
87
+ const worktreesFirstRender = useRef(true);
88
+ useEffect(() => {
89
+ if (worktreesFirstRender.current) {
90
+ worktreesFirstRender.current = false;
91
+ return;
92
+ }
93
+ onToggleAllWorktrees?.(showAllWorktrees ?? false);
94
+ }, [showAllWorktrees, onToggleAllWorktrees]);
95
+
43
96
  useEffect(() => {
44
97
  if (pendingDecision === "select") {
45
98
  if (items.length > 0 && selectedIndex < items.length) {
@@ -82,6 +135,20 @@ export const SessionSelector: React.FC<SessionSelectorProps> = ({
82
135
  startIndex + MAX_VISIBLE_ITEMS,
83
136
  );
84
137
 
138
+ const hints = [
139
+ onToggleAllProjects
140
+ ? `Ctrl+A ${showAllProjects ? "current directory" : "all projects"}`
141
+ : null,
142
+ onToggleAllWorktrees && worktreePaths.length > 1
143
+ ? `Ctrl+W ${showAllWorktrees ? "current worktree" : "all worktrees"}`
144
+ : null,
145
+ "↑↓ select",
146
+ "Enter confirm",
147
+ "Esc cancel",
148
+ ]
149
+ .filter((hint): hint is string => hint !== null)
150
+ .join(" · ");
151
+
85
152
  return (
86
153
  <Box
87
154
  flexDirection="column"
@@ -124,8 +191,12 @@ export const SessionSelector: React.FC<SessionSelectorProps> = ({
124
191
  color={isSelected ? "black" : "white"}
125
192
  wrap="truncate-end"
126
193
  >
127
- {session.id} | {lastActiveAt} | {session.latestTotalTokens}{" "}
128
- tokens
194
+ {session.id.slice(0, 8)} | {lastActiveAt} |{" "}
195
+ {session.latestTotalTokens} tokens
196
+ {session.branch ? ` | [${session.branch}]` : ""}
197
+ {showProjectPath && session.workdir
198
+ ? ` | ${session.workdir}`
199
+ : ""}
129
200
  </Text>
130
201
  </Box>
131
202
  </Box>
@@ -150,7 +221,7 @@ export const SessionSelector: React.FC<SessionSelectorProps> = ({
150
221
  )}
151
222
 
152
223
  <Box>
153
- <Text dimColor>↑↓ navigate • Enter to select • Esc to cancel</Text>
224
+ <Text dimColor>{hints}</Text>
154
225
  </Box>
155
226
  </Box>
156
227
  );