wave-code 1.0.10 → 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 (242) 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 +156 -335
  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/utils/clipboard.ts +21 -0
  31. package/src/utils/highlightUtils.ts +46 -1
  32. package/src/utils/readPackageJson.ts +29 -0
  33. package/src/utils/streamingText.ts +11 -0
  34. package/src/utils/worktree.ts +191 -43
  35. package/dist/cli.d.ts +0 -6
  36. package/dist/cli.js +0 -53
  37. package/dist/commands/plugin/disable.d.ts +0 -5
  38. package/dist/commands/plugin/disable.js +0 -15
  39. package/dist/commands/plugin/enable.d.ts +0 -5
  40. package/dist/commands/plugin/enable.js +0 -15
  41. package/dist/commands/plugin/install.d.ts +0 -5
  42. package/dist/commands/plugin/install.js +0 -21
  43. package/dist/commands/plugin/list.d.ts +0 -1
  44. package/dist/commands/plugin/list.js +0 -31
  45. package/dist/commands/plugin/marketplace.d.ts +0 -10
  46. package/dist/commands/plugin/marketplace.js +0 -90
  47. package/dist/commands/plugin/uninstall.d.ts +0 -3
  48. package/dist/commands/plugin/uninstall.js +0 -16
  49. package/dist/commands/plugin/update.d.ts +0 -3
  50. package/dist/commands/plugin/update.js +0 -15
  51. package/dist/commands/update.d.ts +0 -1
  52. package/dist/commands/update.js +0 -97
  53. package/dist/components/AgentsManager.d.ts +0 -7
  54. package/dist/components/AgentsManager.js +0 -109
  55. package/dist/components/App.d.ts +0 -9
  56. package/dist/components/App.js +0 -76
  57. package/dist/components/BackgroundTaskManager.d.ts +0 -5
  58. package/dist/components/BackgroundTaskManager.js +0 -99
  59. package/dist/components/BangDisplay.d.ts +0 -8
  60. package/dist/components/BangDisplay.js +0 -17
  61. package/dist/components/BtwDisplay.d.ts +0 -7
  62. package/dist/components/BtwDisplay.js +0 -19
  63. package/dist/components/ChatInterface.d.ts +0 -2
  64. package/dist/components/ChatInterface.js +0 -61
  65. package/dist/components/CommandSelector.d.ts +0 -10
  66. package/dist/components/CommandSelector.js +0 -66
  67. package/dist/components/CompactDisplay.d.ts +0 -8
  68. package/dist/components/CompactDisplay.js +0 -13
  69. package/dist/components/ConfirmationDetails.d.ts +0 -9
  70. package/dist/components/ConfirmationDetails.js +0 -45
  71. package/dist/components/ConfirmationSelector.d.ts +0 -13
  72. package/dist/components/ConfirmationSelector.js +0 -127
  73. package/dist/components/DiffDisplay.d.ts +0 -8
  74. package/dist/components/DiffDisplay.js +0 -201
  75. package/dist/components/DiscoverView.d.ts +0 -2
  76. package/dist/components/DiscoverView.js +0 -37
  77. package/dist/components/FileSelector.d.ts +0 -11
  78. package/dist/components/FileSelector.js +0 -50
  79. package/dist/components/HelpView.d.ts +0 -7
  80. package/dist/components/HelpView.js +0 -74
  81. package/dist/components/HistorySearch.d.ts +0 -8
  82. package/dist/components/HistorySearch.js +0 -66
  83. package/dist/components/InputBox.d.ts +0 -24
  84. package/dist/components/InputBox.js +0 -149
  85. package/dist/components/InstalledView.d.ts +0 -2
  86. package/dist/components/InstalledView.js +0 -45
  87. package/dist/components/LoadingIndicator.d.ts +0 -10
  88. package/dist/components/LoadingIndicator.js +0 -6
  89. package/dist/components/LoginCommand.d.ts +0 -5
  90. package/dist/components/LoginCommand.js +0 -153
  91. package/dist/components/Markdown.d.ts +0 -5
  92. package/dist/components/Markdown.js +0 -127
  93. package/dist/components/MarketplaceAddForm.d.ts +0 -2
  94. package/dist/components/MarketplaceAddForm.js +0 -55
  95. package/dist/components/MarketplaceDetail.d.ts +0 -2
  96. package/dist/components/MarketplaceDetail.js +0 -74
  97. package/dist/components/MarketplaceList.d.ts +0 -8
  98. package/dist/components/MarketplaceList.js +0 -17
  99. package/dist/components/MarketplaceView.d.ts +0 -2
  100. package/dist/components/MarketplaceView.js +0 -41
  101. package/dist/components/McpManager.d.ts +0 -9
  102. package/dist/components/McpManager.js +0 -88
  103. package/dist/components/MessageBlockItem.d.ts +0 -8
  104. package/dist/components/MessageBlockItem.js +0 -15
  105. package/dist/components/MessageList.d.ts +0 -10
  106. package/dist/components/MessageList.js +0 -80
  107. package/dist/components/ModelSelector.d.ts +0 -8
  108. package/dist/components/ModelSelector.js +0 -45
  109. package/dist/components/Notifications.d.ts +0 -7
  110. package/dist/components/Notifications.js +0 -9
  111. package/dist/components/PlanDisplay.d.ts +0 -7
  112. package/dist/components/PlanDisplay.js +0 -6
  113. package/dist/components/PluginDetail.d.ts +0 -2
  114. package/dist/components/PluginDetail.js +0 -87
  115. package/dist/components/PluginList.d.ts +0 -13
  116. package/dist/components/PluginList.js +0 -15
  117. package/dist/components/PluginManagerShell.d.ts +0 -6
  118. package/dist/components/PluginManagerShell.js +0 -92
  119. package/dist/components/PluginManagerTypes.d.ts +0 -35
  120. package/dist/components/PluginManagerTypes.js +0 -1
  121. package/dist/components/QueuedMessageList.d.ts +0 -2
  122. package/dist/components/QueuedMessageList.js +0 -18
  123. package/dist/components/ReasoningDisplay.d.ts +0 -8
  124. package/dist/components/ReasoningDisplay.js +0 -13
  125. package/dist/components/RewindCommand.d.ts +0 -12
  126. package/dist/components/RewindCommand.js +0 -78
  127. package/dist/components/SessionSelector.d.ts +0 -12
  128. package/dist/components/SessionSelector.js +0 -47
  129. package/dist/components/StatusCommand.d.ts +0 -5
  130. package/dist/components/StatusCommand.js +0 -28
  131. package/dist/components/StatusLine.d.ts +0 -6
  132. package/dist/components/StatusLine.js +0 -9
  133. package/dist/components/TaskList.d.ts +0 -5
  134. package/dist/components/TaskList.js +0 -197
  135. package/dist/components/TaskNotificationMessage.d.ts +0 -5
  136. package/dist/components/TaskNotificationMessage.js +0 -12
  137. package/dist/components/ToolDisplay.d.ts +0 -9
  138. package/dist/components/ToolDisplay.js +0 -55
  139. package/dist/components/WorkflowManager.d.ts +0 -5
  140. package/dist/components/WorkflowManager.js +0 -102
  141. package/dist/components/WorktreeExitPrompt.d.ts +0 -12
  142. package/dist/components/WorktreeExitPrompt.js +0 -35
  143. package/dist/constants/commands.d.ts +0 -2
  144. package/dist/constants/commands.js +0 -92
  145. package/dist/contexts/PluginManagerContext.d.ts +0 -3
  146. package/dist/contexts/PluginManagerContext.js +0 -9
  147. package/dist/contexts/useAppConfig.d.ts +0 -10
  148. package/dist/contexts/useAppConfig.js +0 -13
  149. package/dist/contexts/useChat.d.ts +0 -101
  150. package/dist/contexts/useChat.js +0 -993
  151. package/dist/contracts/status.d.ts +0 -7
  152. package/dist/contracts/status.js +0 -1
  153. package/dist/daemon/commands.d.ts +0 -49
  154. package/dist/daemon/commands.js +0 -341
  155. package/dist/daemon/jsonRpcClient.d.ts +0 -38
  156. package/dist/daemon/jsonRpcClient.js +0 -129
  157. package/dist/daemon/socketClient.d.ts +0 -13
  158. package/dist/daemon/socketClient.js +0 -26
  159. package/dist/daemon-cli.d.ts +0 -10
  160. package/dist/daemon-cli.js +0 -15
  161. package/dist/hooks/useInputManager.d.ts +0 -75
  162. package/dist/hooks/useInputManager.js +0 -583
  163. package/dist/hooks/usePluginManager.d.ts +0 -4
  164. package/dist/hooks/usePluginManager.js +0 -265
  165. package/dist/hooks/useTasks.d.ts +0 -1
  166. package/dist/hooks/useTasks.js +0 -5
  167. package/dist/index.d.ts +0 -4
  168. package/dist/index.js +0 -447
  169. package/dist/managers/inputHandlers.d.ts +0 -30
  170. package/dist/managers/inputHandlers.js +0 -625
  171. package/dist/managers/inputReducer.d.ts +0 -271
  172. package/dist/managers/inputReducer.js +0 -1121
  173. package/dist/plugin-manager-cli.d.ts +0 -5
  174. package/dist/plugin-manager-cli.js +0 -12
  175. package/dist/print-cli.d.ts +0 -10
  176. package/dist/print-cli.js +0 -209
  177. package/dist/reducers/agentsManagerReducer.d.ts +0 -26
  178. package/dist/reducers/agentsManagerReducer.js +0 -54
  179. package/dist/reducers/backgroundTaskManagerReducer.d.ts +0 -62
  180. package/dist/reducers/backgroundTaskManagerReducer.js +0 -106
  181. package/dist/reducers/confirmationReducer.d.ts +0 -37
  182. package/dist/reducers/confirmationReducer.js +0 -193
  183. package/dist/reducers/helpSelectorReducer.d.ts +0 -25
  184. package/dist/reducers/helpSelectorReducer.js +0 -52
  185. package/dist/reducers/marketplaceAddFormReducer.d.ts +0 -24
  186. package/dist/reducers/marketplaceAddFormReducer.js +0 -58
  187. package/dist/reducers/mcpManagerReducer.d.ts +0 -37
  188. package/dist/reducers/mcpManagerReducer.js +0 -74
  189. package/dist/reducers/pluginDetailReducer.d.ts +0 -32
  190. package/dist/reducers/pluginDetailReducer.js +0 -70
  191. package/dist/reducers/questionReducer.d.ts +0 -82
  192. package/dist/reducers/questionReducer.js +0 -375
  193. package/dist/reducers/rewindSelectorReducer.d.ts +0 -21
  194. package/dist/reducers/rewindSelectorReducer.js +0 -37
  195. package/dist/reducers/selectorReducer.d.ts +0 -23
  196. package/dist/reducers/selectorReducer.js +0 -42
  197. package/dist/reducers/workflowManagerReducer.d.ts +0 -40
  198. package/dist/reducers/workflowManagerReducer.js +0 -100
  199. package/dist/session-selector-cli.d.ts +0 -3
  200. package/dist/session-selector-cli.js +0 -25
  201. package/dist/stdio/agentBridge.d.ts +0 -139
  202. package/dist/stdio/agentBridge.js +0 -999
  203. package/dist/stdio/daemonServer.d.ts +0 -67
  204. package/dist/stdio/daemonServer.js +0 -191
  205. package/dist/stdio/index.d.ts +0 -5
  206. package/dist/stdio/index.js +0 -5
  207. package/dist/stdio/jsonRpcConnection.d.ts +0 -39
  208. package/dist/stdio/jsonRpcConnection.js +0 -135
  209. package/dist/stdio/protocol.d.ts +0 -41
  210. package/dist/stdio/protocol.js +0 -26
  211. package/dist/stdio/stdioServer.d.ts +0 -36
  212. package/dist/stdio/stdioServer.js +0 -51
  213. package/dist/stdio-cli.d.ts +0 -8
  214. package/dist/stdio-cli.js +0 -15
  215. package/dist/types.d.ts +0 -20
  216. package/dist/types.js +0 -1
  217. package/dist/utils/bracketedPaste.d.ts +0 -39
  218. package/dist/utils/bracketedPaste.js +0 -122
  219. package/dist/utils/clipboard.d.ts +0 -21
  220. package/dist/utils/clipboard.js +0 -347
  221. package/dist/utils/constants.d.ts +0 -16
  222. package/dist/utils/constants.js +0 -18
  223. package/dist/utils/highlightUtils.d.ts +0 -1
  224. package/dist/utils/highlightUtils.js +0 -93
  225. package/dist/utils/inputUtils.d.ts +0 -17
  226. package/dist/utils/inputUtils.js +0 -67
  227. package/dist/utils/logger.d.ts +0 -71
  228. package/dist/utils/logger.js +0 -259
  229. package/dist/utils/markdownTable.d.ts +0 -34
  230. package/dist/utils/markdownTable.js +0 -302
  231. package/dist/utils/rewindCheckpoints.d.ts +0 -8
  232. package/dist/utils/rewindCheckpoints.js +0 -17
  233. package/dist/utils/throttle.d.ts +0 -14
  234. package/dist/utils/throttle.js +0 -55
  235. package/dist/utils/toolParameterTransforms.d.ts +0 -18
  236. package/dist/utils/toolParameterTransforms.js +0 -73
  237. package/dist/utils/usageSummary.d.ts +0 -28
  238. package/dist/utils/usageSummary.js +0 -121
  239. package/dist/utils/version.d.ts +0 -1
  240. package/dist/utils/version.js +0 -4
  241. package/dist/utils/worktree.d.ts +0 -35
  242. 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 */}