wave-code 1.0.9 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/bin/wave-code.js +8 -2
  2. package/dist/bundle/wave.mjs +3207 -0
  3. package/package.json +24 -25
  4. package/src/commands/update.ts +2 -7
  5. package/src/components/App.tsx +3 -7
  6. package/src/components/BtwDisplay.tsx +10 -1
  7. package/src/components/ChatInterface.tsx +21 -52
  8. package/src/components/ConfirmationDetails.tsx +144 -37
  9. package/src/components/DiffDisplay.tsx +335 -301
  10. package/src/components/InputBox.tsx +3 -1
  11. package/src/components/LoadingIndicator.tsx +16 -2
  12. package/src/components/MessageBlockItem.tsx +2 -4
  13. package/src/components/MessageList.tsx +75 -14
  14. package/src/components/ReasoningDisplay.tsx +2 -4
  15. package/src/components/SessionSelector.tsx +82 -11
  16. package/src/components/StatusCommand.tsx +2 -9
  17. package/src/components/ToolDisplay.tsx +2 -4
  18. package/src/contexts/useChat.tsx +172 -350
  19. package/src/daemon/commands.ts +57 -27
  20. package/src/daemon/jsonRpcClient.ts +1 -1
  21. package/src/daemon/socketClient.ts +2 -2
  22. package/src/hooks/useInputManager.ts +1 -1
  23. package/src/index.ts +57 -10
  24. package/src/managers/inputReducer.ts +186 -104
  25. package/src/print-cli.ts +21 -17
  26. package/src/reducers/selectorReducer.ts +10 -0
  27. package/src/session-selector-cli.tsx +269 -28
  28. package/src/stdio/agentBridge.ts +22 -21
  29. package/src/stdio/daemonServer.ts +3 -1
  30. package/src/stdio/jsonRpcConnection.ts +19 -1
  31. package/src/stdio/stdioServer.ts +17 -0
  32. package/src/utils/clipboard.ts +21 -0
  33. package/src/utils/highlightUtils.ts +46 -1
  34. package/src/utils/readPackageJson.ts +29 -0
  35. package/src/utils/streamingText.ts +11 -0
  36. package/src/utils/worktree.ts +191 -43
  37. package/dist/cli.d.ts +0 -6
  38. package/dist/cli.js +0 -53
  39. package/dist/commands/plugin/disable.d.ts +0 -5
  40. package/dist/commands/plugin/disable.js +0 -15
  41. package/dist/commands/plugin/enable.d.ts +0 -5
  42. package/dist/commands/plugin/enable.js +0 -15
  43. package/dist/commands/plugin/install.d.ts +0 -5
  44. package/dist/commands/plugin/install.js +0 -21
  45. package/dist/commands/plugin/list.d.ts +0 -1
  46. package/dist/commands/plugin/list.js +0 -31
  47. package/dist/commands/plugin/marketplace.d.ts +0 -10
  48. package/dist/commands/plugin/marketplace.js +0 -90
  49. package/dist/commands/plugin/uninstall.d.ts +0 -3
  50. package/dist/commands/plugin/uninstall.js +0 -16
  51. package/dist/commands/plugin/update.d.ts +0 -3
  52. package/dist/commands/plugin/update.js +0 -15
  53. package/dist/commands/update.d.ts +0 -1
  54. package/dist/commands/update.js +0 -97
  55. package/dist/components/AgentsManager.d.ts +0 -7
  56. package/dist/components/AgentsManager.js +0 -109
  57. package/dist/components/App.d.ts +0 -9
  58. package/dist/components/App.js +0 -76
  59. package/dist/components/BackgroundTaskManager.d.ts +0 -5
  60. package/dist/components/BackgroundTaskManager.js +0 -99
  61. package/dist/components/BangDisplay.d.ts +0 -8
  62. package/dist/components/BangDisplay.js +0 -17
  63. package/dist/components/BtwDisplay.d.ts +0 -7
  64. package/dist/components/BtwDisplay.js +0 -19
  65. package/dist/components/ChatInterface.d.ts +0 -2
  66. package/dist/components/ChatInterface.js +0 -61
  67. package/dist/components/CommandSelector.d.ts +0 -10
  68. package/dist/components/CommandSelector.js +0 -66
  69. package/dist/components/CompactDisplay.d.ts +0 -8
  70. package/dist/components/CompactDisplay.js +0 -13
  71. package/dist/components/ConfirmationDetails.d.ts +0 -9
  72. package/dist/components/ConfirmationDetails.js +0 -45
  73. package/dist/components/ConfirmationSelector.d.ts +0 -13
  74. package/dist/components/ConfirmationSelector.js +0 -127
  75. package/dist/components/DiffDisplay.d.ts +0 -8
  76. package/dist/components/DiffDisplay.js +0 -201
  77. package/dist/components/DiscoverView.d.ts +0 -2
  78. package/dist/components/DiscoverView.js +0 -37
  79. package/dist/components/FileSelector.d.ts +0 -11
  80. package/dist/components/FileSelector.js +0 -50
  81. package/dist/components/HelpView.d.ts +0 -7
  82. package/dist/components/HelpView.js +0 -74
  83. package/dist/components/HistorySearch.d.ts +0 -8
  84. package/dist/components/HistorySearch.js +0 -66
  85. package/dist/components/InputBox.d.ts +0 -24
  86. package/dist/components/InputBox.js +0 -149
  87. package/dist/components/InstalledView.d.ts +0 -2
  88. package/dist/components/InstalledView.js +0 -45
  89. package/dist/components/LoadingIndicator.d.ts +0 -10
  90. package/dist/components/LoadingIndicator.js +0 -6
  91. package/dist/components/LoginCommand.d.ts +0 -5
  92. package/dist/components/LoginCommand.js +0 -153
  93. package/dist/components/Markdown.d.ts +0 -5
  94. package/dist/components/Markdown.js +0 -127
  95. package/dist/components/MarketplaceAddForm.d.ts +0 -2
  96. package/dist/components/MarketplaceAddForm.js +0 -55
  97. package/dist/components/MarketplaceDetail.d.ts +0 -2
  98. package/dist/components/MarketplaceDetail.js +0 -74
  99. package/dist/components/MarketplaceList.d.ts +0 -8
  100. package/dist/components/MarketplaceList.js +0 -17
  101. package/dist/components/MarketplaceView.d.ts +0 -2
  102. package/dist/components/MarketplaceView.js +0 -41
  103. package/dist/components/McpManager.d.ts +0 -9
  104. package/dist/components/McpManager.js +0 -88
  105. package/dist/components/MessageBlockItem.d.ts +0 -8
  106. package/dist/components/MessageBlockItem.js +0 -15
  107. package/dist/components/MessageList.d.ts +0 -10
  108. package/dist/components/MessageList.js +0 -80
  109. package/dist/components/ModelSelector.d.ts +0 -8
  110. package/dist/components/ModelSelector.js +0 -45
  111. package/dist/components/Notifications.d.ts +0 -7
  112. package/dist/components/Notifications.js +0 -9
  113. package/dist/components/PlanDisplay.d.ts +0 -7
  114. package/dist/components/PlanDisplay.js +0 -6
  115. package/dist/components/PluginDetail.d.ts +0 -2
  116. package/dist/components/PluginDetail.js +0 -87
  117. package/dist/components/PluginList.d.ts +0 -13
  118. package/dist/components/PluginList.js +0 -15
  119. package/dist/components/PluginManagerShell.d.ts +0 -6
  120. package/dist/components/PluginManagerShell.js +0 -92
  121. package/dist/components/PluginManagerTypes.d.ts +0 -35
  122. package/dist/components/PluginManagerTypes.js +0 -1
  123. package/dist/components/QueuedMessageList.d.ts +0 -2
  124. package/dist/components/QueuedMessageList.js +0 -18
  125. package/dist/components/ReasoningDisplay.d.ts +0 -8
  126. package/dist/components/ReasoningDisplay.js +0 -13
  127. package/dist/components/RewindCommand.d.ts +0 -12
  128. package/dist/components/RewindCommand.js +0 -78
  129. package/dist/components/SessionSelector.d.ts +0 -12
  130. package/dist/components/SessionSelector.js +0 -47
  131. package/dist/components/StatusCommand.d.ts +0 -5
  132. package/dist/components/StatusCommand.js +0 -28
  133. package/dist/components/StatusLine.d.ts +0 -6
  134. package/dist/components/StatusLine.js +0 -9
  135. package/dist/components/TaskList.d.ts +0 -5
  136. package/dist/components/TaskList.js +0 -197
  137. package/dist/components/TaskNotificationMessage.d.ts +0 -5
  138. package/dist/components/TaskNotificationMessage.js +0 -12
  139. package/dist/components/ToolDisplay.d.ts +0 -9
  140. package/dist/components/ToolDisplay.js +0 -55
  141. package/dist/components/WorkflowManager.d.ts +0 -5
  142. package/dist/components/WorkflowManager.js +0 -102
  143. package/dist/components/WorktreeExitPrompt.d.ts +0 -12
  144. package/dist/components/WorktreeExitPrompt.js +0 -35
  145. package/dist/constants/commands.d.ts +0 -2
  146. package/dist/constants/commands.js +0 -92
  147. package/dist/contexts/PluginManagerContext.d.ts +0 -3
  148. package/dist/contexts/PluginManagerContext.js +0 -9
  149. package/dist/contexts/useAppConfig.d.ts +0 -10
  150. package/dist/contexts/useAppConfig.js +0 -13
  151. package/dist/contexts/useChat.d.ts +0 -101
  152. package/dist/contexts/useChat.js +0 -992
  153. package/dist/contracts/status.d.ts +0 -7
  154. package/dist/contracts/status.js +0 -1
  155. package/dist/daemon/commands.d.ts +0 -49
  156. package/dist/daemon/commands.js +0 -341
  157. package/dist/daemon/jsonRpcClient.d.ts +0 -38
  158. package/dist/daemon/jsonRpcClient.js +0 -129
  159. package/dist/daemon/socketClient.d.ts +0 -13
  160. package/dist/daemon/socketClient.js +0 -26
  161. package/dist/daemon-cli.d.ts +0 -10
  162. package/dist/daemon-cli.js +0 -15
  163. package/dist/hooks/useInputManager.d.ts +0 -75
  164. package/dist/hooks/useInputManager.js +0 -583
  165. package/dist/hooks/usePluginManager.d.ts +0 -4
  166. package/dist/hooks/usePluginManager.js +0 -265
  167. package/dist/hooks/useTasks.d.ts +0 -1
  168. package/dist/hooks/useTasks.js +0 -5
  169. package/dist/index.d.ts +0 -4
  170. package/dist/index.js +0 -447
  171. package/dist/managers/inputHandlers.d.ts +0 -30
  172. package/dist/managers/inputHandlers.js +0 -625
  173. package/dist/managers/inputReducer.d.ts +0 -271
  174. package/dist/managers/inputReducer.js +0 -1121
  175. package/dist/plugin-manager-cli.d.ts +0 -5
  176. package/dist/plugin-manager-cli.js +0 -12
  177. package/dist/print-cli.d.ts +0 -10
  178. package/dist/print-cli.js +0 -209
  179. package/dist/reducers/agentsManagerReducer.d.ts +0 -26
  180. package/dist/reducers/agentsManagerReducer.js +0 -54
  181. package/dist/reducers/backgroundTaskManagerReducer.d.ts +0 -62
  182. package/dist/reducers/backgroundTaskManagerReducer.js +0 -106
  183. package/dist/reducers/confirmationReducer.d.ts +0 -37
  184. package/dist/reducers/confirmationReducer.js +0 -193
  185. package/dist/reducers/helpSelectorReducer.d.ts +0 -25
  186. package/dist/reducers/helpSelectorReducer.js +0 -52
  187. package/dist/reducers/marketplaceAddFormReducer.d.ts +0 -24
  188. package/dist/reducers/marketplaceAddFormReducer.js +0 -58
  189. package/dist/reducers/mcpManagerReducer.d.ts +0 -37
  190. package/dist/reducers/mcpManagerReducer.js +0 -74
  191. package/dist/reducers/pluginDetailReducer.d.ts +0 -32
  192. package/dist/reducers/pluginDetailReducer.js +0 -70
  193. package/dist/reducers/questionReducer.d.ts +0 -82
  194. package/dist/reducers/questionReducer.js +0 -375
  195. package/dist/reducers/rewindSelectorReducer.d.ts +0 -21
  196. package/dist/reducers/rewindSelectorReducer.js +0 -37
  197. package/dist/reducers/selectorReducer.d.ts +0 -23
  198. package/dist/reducers/selectorReducer.js +0 -42
  199. package/dist/reducers/workflowManagerReducer.d.ts +0 -40
  200. package/dist/reducers/workflowManagerReducer.js +0 -100
  201. package/dist/session-selector-cli.d.ts +0 -3
  202. package/dist/session-selector-cli.js +0 -25
  203. package/dist/stdio/agentBridge.d.ts +0 -139
  204. package/dist/stdio/agentBridge.js +0 -999
  205. package/dist/stdio/daemonServer.d.ts +0 -67
  206. package/dist/stdio/daemonServer.js +0 -191
  207. package/dist/stdio/index.d.ts +0 -5
  208. package/dist/stdio/index.js +0 -5
  209. package/dist/stdio/jsonRpcConnection.d.ts +0 -30
  210. package/dist/stdio/jsonRpcConnection.js +0 -127
  211. package/dist/stdio/protocol.d.ts +0 -41
  212. package/dist/stdio/protocol.js +0 -26
  213. package/dist/stdio/stdioServer.d.ts +0 -28
  214. package/dist/stdio/stdioServer.js +0 -36
  215. package/dist/stdio-cli.d.ts +0 -8
  216. package/dist/stdio-cli.js +0 -15
  217. package/dist/types.d.ts +0 -20
  218. package/dist/types.js +0 -1
  219. package/dist/utils/bracketedPaste.d.ts +0 -39
  220. package/dist/utils/bracketedPaste.js +0 -122
  221. package/dist/utils/clipboard.d.ts +0 -21
  222. package/dist/utils/clipboard.js +0 -347
  223. package/dist/utils/constants.d.ts +0 -16
  224. package/dist/utils/constants.js +0 -18
  225. package/dist/utils/highlightUtils.d.ts +0 -1
  226. package/dist/utils/highlightUtils.js +0 -93
  227. package/dist/utils/inputUtils.d.ts +0 -17
  228. package/dist/utils/inputUtils.js +0 -67
  229. package/dist/utils/logger.d.ts +0 -71
  230. package/dist/utils/logger.js +0 -259
  231. package/dist/utils/markdownTable.d.ts +0 -34
  232. package/dist/utils/markdownTable.js +0 -302
  233. package/dist/utils/rewindCheckpoints.d.ts +0 -8
  234. package/dist/utils/rewindCheckpoints.js +0 -17
  235. package/dist/utils/throttle.d.ts +0 -14
  236. package/dist/utils/throttle.js +0 -55
  237. package/dist/utils/toolParameterTransforms.d.ts +0 -18
  238. package/dist/utils/toolParameterTransforms.js +0 -73
  239. package/dist/utils/usageSummary.d.ts +0 -28
  240. package/dist/utils/usageSummary.js +0 -121
  241. package/dist/utils/version.d.ts +0 -1
  242. package/dist/utils/version.js +0 -4
  243. package/dist/utils/worktree.d.ts +0 -35
  244. package/dist/utils/worktree.js +0 -219
@@ -369,7 +369,9 @@ export const InputBox: React.FC<InputBoxProps> = ({
369
369
  showModelSelector ||
370
370
  showWorkflowManager || (
371
371
  <Box flexDirection="column">
372
- {escClearPending && <Text color="gray">再次按 Esc 清空输入</Text>}
372
+ {escClearPending && (
373
+ <Text color="gray">Press Esc again to clear input</Text>
374
+ )}
373
375
  <Box
374
376
  borderStyle="single"
375
377
  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
  );
@@ -1,12 +1,7 @@
1
1
  import React from "react";
2
2
  import { Box, Text, useInput } from "ink";
3
3
  import { useChat } from "../contexts/useChat.js";
4
- import fs from "fs";
5
- import path from "path";
6
- import { fileURLToPath } from "url";
7
-
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = path.dirname(__filename);
4
+ import { readNearestPackageJson } from "../utils/readPackageJson.js";
10
5
 
11
6
  export interface StatusCommandProps {
12
7
  onCancel: () => void;
@@ -27,9 +22,7 @@ export const StatusCommand: React.FC<StatusCommandProps> = ({ onCancel }) => {
27
22
 
28
23
  let version = "unknown";
29
24
  try {
30
- const pkgPath = path.resolve(__dirname, "../../package.json");
31
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
32
- version = pkg.version;
25
+ version = readNearestPackageJson().version;
33
26
  } catch {
34
27
  // Fallback if package.json cannot be read
35
28
  }
@@ -3,6 +3,7 @@ import { Box, Text } from "ink";
3
3
  import type { ToolBlock } from "wave-agent-sdk";
4
4
  import { getLastLines } from "wave-agent-sdk";
5
5
  import { DiffDisplay } from "./DiffDisplay.js";
6
+ import { streamingTail } from "../utils/streamingText.js";
6
7
 
7
8
  interface ToolDisplayProps {
8
9
  block: ToolBlock;
@@ -81,10 +82,7 @@ export const ToolDisplay: React.FC<ToolDisplayProps> = ({
81
82
  parameters &&
82
83
  !compactParams && (
83
84
  <Text color="gray" wrap="truncate-end">
84
- {` ${(() => {
85
- const flat = parameters.replace(/\n/g, "\\n");
86
- return flat.length > 30 ? `…${flat.slice(-30)}` : flat;
87
- })()}`}
85
+ {` ${streamingTail(parameters)}`}
88
86
  </Text>
89
87
  )}
90
88
  {/* Display image indicator */}