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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-code",
3
- "version": "1.0.9",
3
+ "version": "1.1.0",
4
4
  "description": "CLI-based code assistant powered by AI, built with React and Ink",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,8 +15,7 @@
15
15
  "ink",
16
16
  "typescript"
17
17
  ],
18
- "main": "dist/index.js",
19
- "types": "dist/index.d.ts",
18
+ "main": "dist/bundle/wave.mjs",
20
19
  "type": "module",
21
20
  "bin": {
22
21
  "wave-code": "bin/wave-code.js",
@@ -29,49 +28,49 @@
29
28
  "README.md"
30
29
  ],
31
30
  "dependencies": {
31
+ "@vscode/ripgrep": "^1.18.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/react": "^19.1.8",
35
+ "@types/semver": "^7.7.1",
36
+ "@types/yargs": "^17.0.0",
37
+ "@vitest/coverage-v8": "^4.1.7",
32
38
  "chalk": "^5.6.2",
33
39
  "diff": "^8.0.2",
40
+ "esbuild": "^0.28.1",
41
+ "eslint-plugin-react": "^7.37.5",
42
+ "eslint-plugin-react-hooks": "^5.2.0",
34
43
  "glob": "^13.0.0",
35
44
  "highlight.js": "^11.11.1",
36
- "ink": "^6.7.0",
45
+ "ink": "^7.1.1",
46
+ "ink-testing-library": "^4.0.0",
37
47
  "marked": "^17.0.2",
38
48
  "node-html-parser": "^7.0.2",
39
49
  "react": "^19.2.4",
40
50
  "react-dom": "19.2.4",
51
+ "rimraf": "^6.1.2",
41
52
  "semver": "^7.7.4",
42
53
  "string-width": "^8.2.2",
54
+ "tsx": "^4.20.4",
55
+ "vitest": "^4.1.7",
43
56
  "wrap-ansi": "^10.0.0",
44
57
  "yargs": "^17.7.2",
45
58
  "zod": "^3.23.8",
46
- "wave-agent-sdk": "1.0.9"
47
- },
48
- "devDependencies": {
49
- "@types/react": "^19.1.8",
50
- "@types/semver": "^7.7.1",
51
- "@types/yargs": "^17.0.0",
52
- "@vitest/coverage-v8": "^4.1.7",
53
- "eslint-plugin-react": "^7.37.5",
54
- "eslint-plugin-react-hooks": "^5.2.0",
55
- "ink-testing-library": "^4.0.0",
56
- "rimraf": "^6.1.2",
57
- "tsc-alias": "^1.8.16",
58
- "tsx": "^4.20.4",
59
- "vitest": "^4.1.7"
60
- },
61
- "peerDependencies": {
62
- "react": ">=18.0.0"
59
+ "wave-agent-sdk": "1.1.0"
63
60
  },
64
61
  "engines": {
65
- "node": ">=20"
62
+ "node": ">=22"
66
63
  },
67
64
  "license": "MIT",
68
65
  "scripts": {
69
- "wave": "NODE_OPTIONS='--heapsnapshot-near-heap-limit=1' tsx --tsconfig tsconfig.dev.json src/index.ts",
70
- "build": "rimraf dist && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
66
+ "wave": "tsx --tsconfig tsconfig.dev.json src/index.ts",
67
+ "build": "rimraf dist && node scripts/bundle.mjs",
71
68
  "type-check": "tsc --noEmit --incremental",
72
- "watch": "tsc -p tsconfig.build.json --watch & tsc-alias -p tsconfig.build.json --watch",
73
69
  "test": "vitest run --reporter=dot",
74
70
  "test:coverage": "vitest run --coverage --reporter=dot",
71
+ "test:unit": "vitest run --reporter=dot --exclude 'tests/integration/**' --exclude '**/*.integration.test.ts'",
72
+ "test:unit:coverage": "vitest run --coverage --reporter=dot --exclude 'tests/integration/**' --exclude '**/*.integration.test.ts'",
73
+ "test:integration": "vitest run --reporter=dot tests/integration .integration.test",
75
74
  "lint": "eslint --cache",
76
75
  "format": "prettier --write ."
77
76
  }
@@ -1,15 +1,10 @@
1
1
  import { spawnSync } from "child_process";
2
- import { readFileSync } from "fs";
3
- import path from "path";
4
- import { fileURLToPath } from "url";
5
2
  import https from "https";
6
3
  import chalk from "chalk";
7
4
  import { isUpdateAvailable } from "../utils/version.js";
5
+ import { readNearestPackageJson } from "../utils/readPackageJson.js";
8
6
 
9
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
- const packageJsonPath = path.resolve(__dirname, "../../package.json");
11
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
12
- const currentVersion = packageJson.version;
7
+ const currentVersion = readNearestPackageJson().version;
13
8
 
14
9
  async function getLatestVersion(): Promise<string> {
15
10
  return new Promise((resolve, reject) => {
@@ -1,7 +1,7 @@
1
1
  import React, { useState, useEffect, useCallback } from "react";
2
2
  import { useInput } from "ink";
3
3
  import { ChatInterface } from "./ChatInterface.js";
4
- import { ChatProvider, useChat } from "../contexts/useChat.js";
4
+ import { ChatProvider } from "../contexts/useChat.js";
5
5
  import { AppProvider } from "../contexts/useAppConfig.js";
6
6
  import { WorktreeExitPrompt } from "./WorktreeExitPrompt.js";
7
7
  import {
@@ -22,12 +22,11 @@ interface AppWithProvidersProps extends BaseAppProps {
22
22
  onExit: (shouldRemove: boolean) => void;
23
23
  }
24
24
 
25
- /** Wraps ChatInterface with worktree exit handling, using useChat() for hook access. */
25
+ /** Wraps ChatInterface with worktree exit handling. */
26
26
  const ChatWithExitPrompt: React.FC<{
27
27
  worktreeSession: NonNullable<BaseAppProps["worktreeSession"]>;
28
28
  onExit: (shouldRemove: boolean) => void;
29
29
  }> = ({ worktreeSession, onExit }) => {
30
- const { triggerWorktreeRemoveHook } = useChat();
31
30
  const [isExiting, setIsExiting] = useState(false);
32
31
  const [worktreeStatus, setWorktreeStatus] = useState<{
33
32
  hasUncommittedChanges: boolean;
@@ -80,10 +79,7 @@ const ChatWithExitPrompt: React.FC<{
80
79
  hasUncommittedChanges={worktreeStatus.hasUncommittedChanges}
81
80
  hasNewCommits={worktreeStatus.hasNewCommits}
82
81
  onKeep={() => onExit(false)}
83
- onRemove={async () => {
84
- await triggerWorktreeRemoveHook(worktreeSession.path);
85
- onExit(true);
86
- }}
82
+ onRemove={() => onExit(true)}
87
83
  onCancel={() => setIsExiting(false)}
88
84
  />
89
85
  );
@@ -2,6 +2,7 @@ import React from "react";
2
2
  import { Box, Text } from "ink";
3
3
  import { Markdown } from "./Markdown.js";
4
4
  import { BtwState } from "../managers/inputReducer.js";
5
+ import { streamingTail } from "../utils/streamingText.js";
5
6
 
6
7
  interface BtwDisplayProps {
7
8
  btwState: BtwState;
@@ -36,7 +37,15 @@ export const BtwDisplay: React.FC<BtwDisplayProps> = ({ btwState }) => {
36
37
  )}
37
38
  <Box marginTop={1} flexDirection="column">
38
39
  {btwState.isLoading ? (
39
- <Text color="gray">✻ Answering...</Text>
40
+ <Box>
41
+ <Text color="gray">✻ Answering</Text>
42
+ {btwState.answer && (
43
+ <Text color="gray" wrap="truncate-end">
44
+ {" "}
45
+ {streamingTail(btwState.answer)}
46
+ </Text>
47
+ )}
48
+ </Box>
40
49
  ) : isError ? (
41
50
  <Text color="error">{answer}</Text>
42
51
  ) : (
@@ -1,6 +1,5 @@
1
- import React, { useState, useRef, useEffect, useCallback } from "react";
2
- import { Box, useStdout, measureElement, Static } from "ink";
3
- import type { DOMElement } from "ink";
1
+ import React, { useState, useEffect, useCallback } from "react";
2
+ import { Box, useWindowSize } from "ink";
4
3
  import { authService } from "wave-agent-sdk";
5
4
  import { MessageList } from "./MessageList.js";
6
5
  import { InputBox } from "./InputBox.js";
@@ -18,6 +17,7 @@ export const ChatInterface: React.FC = () => {
18
17
  isLoading,
19
18
  isCommandRunning,
20
19
  isCompacting,
20
+ compactionStream,
21
21
  sendMessage,
22
22
  abortMessage,
23
23
  mcpServers,
@@ -30,23 +30,26 @@ export const ChatInterface: React.FC = () => {
30
30
  slashCommands,
31
31
  hasSlashCommand,
32
32
  isConfirmationVisible,
33
- hasPendingConfirmations,
34
33
  confirmingTool,
35
34
  handleConfirmationDecision,
36
35
  handleConfirmationCancel,
37
36
  version,
38
37
  workdir,
39
38
  remountKey,
40
- forceRemount,
41
39
  getGatewayConfig,
42
40
  } = useChat();
43
41
 
44
42
  const displayMessages = messages;
45
43
 
46
- const [forceStatic, setForceStatic] = useState(false);
47
- const { stdout } = useStdout();
48
- const terminalHeight = stdout?.rows ?? 24;
49
- const chatInterfaceRef = useRef<DOMElement>(null);
44
+ const { rows } = useWindowSize();
45
+
46
+ // Reserve terminal rows for the pending tool block in the message list and
47
+ // the ConfirmationSelector, so the confirmation details stay within Ink's
48
+ // incremental-rendering height. When dynamic output >= terminal rows, Ink
49
+ // falls back to a fullscreen clear + redraw on every frame, which makes the
50
+ // confirmation options flicker while navigating.
51
+ const CONFIRMATION_UI_RESERVE = 14;
52
+ const detailsMaxHeight = Math.max(rows - CONFIRMATION_UI_RESERVE, 1);
50
53
 
51
54
  // Compute whether the user has any usable auth/direct-API config,
52
55
  // so the welcome page can prompt /login when neither is present.
@@ -70,36 +73,16 @@ export const ChatInterface: React.FC = () => {
70
73
 
71
74
  const showLoginHint = !hasAuth;
72
75
 
73
- // Handle forceStatic mode for overflow and request remount when exiting
74
- useEffect(() => {
75
- if (isConfirmationVisible && chatInterfaceRef.current) {
76
- const { height } = measureElement(chatInterfaceRef.current);
77
- if (height > terminalHeight) {
78
- setForceStatic(true);
79
- }
80
- } else if (forceStatic && !hasPendingConfirmations) {
81
- setForceStatic(false);
82
- forceRemount();
83
- }
84
- }, [
85
- isConfirmationVisible,
86
- terminalHeight,
87
- forceStatic,
88
- hasPendingConfirmations,
89
- forceRemount,
90
- ]);
91
-
92
76
  if (!sessionId) return null;
93
77
 
94
78
  return (
95
- <Box ref={chatInterfaceRef} flexDirection="column">
79
+ <Box flexDirection="column">
96
80
  <MessageList
97
81
  key={remountKey}
98
82
  messages={displayMessages}
99
83
  isExpanded={isExpanded}
100
84
  version={version}
101
85
  workdir={workdir}
102
- forceStatic={forceStatic}
103
86
  />
104
87
 
105
88
  {!isConfirmationVisible && !isExpanded && (
@@ -110,6 +93,7 @@ export const ChatInterface: React.FC = () => {
110
93
  isCommandRunning={isCommandRunning}
111
94
  isCompacting={isCompacting}
112
95
  latestTotalTokens={latestTotalTokens}
96
+ compactionStream={compactionStream}
113
97
  />
114
98
  )}
115
99
  <TaskList />
@@ -134,28 +118,13 @@ export const ChatInterface: React.FC = () => {
134
118
 
135
119
  {isConfirmationVisible && (
136
120
  <>
137
- {forceStatic ? (
138
- <Static items={[{ key: "confirmation-details" }]}>
139
- {() => (
140
- <ConfirmationDetails
141
- key="confirmation-details"
142
- toolName={confirmingTool!.name}
143
- toolInput={confirmingTool!.input}
144
- planContent={confirmingTool!.planContent}
145
- warning={confirmingTool!.warning}
146
- isExpanded={isExpanded}
147
- />
148
- )}
149
- </Static>
150
- ) : (
151
- <ConfirmationDetails
152
- toolName={confirmingTool!.name}
153
- toolInput={confirmingTool!.input}
154
- planContent={confirmingTool!.planContent}
155
- warning={confirmingTool!.warning}
156
- isExpanded={isExpanded}
157
- />
158
- )}
121
+ <ConfirmationDetails
122
+ toolName={confirmingTool!.name}
123
+ toolInput={confirmingTool!.input}
124
+ planContent={confirmingTool!.planContent}
125
+ warning={confirmingTool!.warning}
126
+ maxHeight={detailsMaxHeight}
127
+ />
159
128
  <ConfirmationSelector
160
129
  toolName={confirmingTool!.name}
161
130
  toolInput={confirmingTool!.input}
@@ -1,5 +1,5 @@
1
- import React from "react";
2
- import { Box, Text } from "ink";
1
+ import React, { useEffect, useState } from "react";
2
+ import { Box, Text, useInput } from "ink";
3
3
  import {
4
4
  BASH_TOOL_NAME,
5
5
  EDIT_TOOL_NAME,
@@ -9,8 +9,7 @@ import {
9
9
  ASK_USER_QUESTION_TOOL_NAME,
10
10
  ARTIFACT_TOOL_NAME,
11
11
  } from "wave-agent-sdk";
12
- import { DiffDisplay } from "./DiffDisplay.js";
13
- import { PlanDisplay } from "./PlanDisplay.js";
12
+ import { buildDiffLinesFromParams } from "./DiffDisplay.js";
14
13
  import { highlightToAnsi } from "../utils/highlightUtils.js";
15
14
 
16
15
  // Helper function to generate descriptive action text
@@ -42,12 +41,20 @@ const getActionDescription = (
42
41
  }
43
42
  };
44
43
 
44
+ // Row budget for the scroll indicators (↑ more / ↓ more, up to one each).
45
+ const SCROLL_INDICATOR_BUDGET = 2;
46
+ // Row budget for the fixed scroll-key hint shown when content is scrollable.
47
+ const SCROLL_HINT_BUDGET = 1;
48
+
45
49
  export interface ConfirmationDetailsProps {
46
50
  toolName: string;
47
51
  toolInput?: Record<string, unknown>;
48
52
  planContent?: string;
49
53
  warning?: string;
50
- isExpanded?: boolean;
54
+ /** Available height (rows) for the whole details block, computed by
55
+ * ChatInterface from the terminal size. Used to size the scrollable
56
+ * content area so the dynamic output never exceeds terminal height. */
57
+ maxHeight?: number;
51
58
  }
52
59
 
53
60
  export const ConfirmationDetails: React.FC<ConfirmationDetailsProps> = ({
@@ -55,56 +62,156 @@ export const ConfirmationDetails: React.FC<ConfirmationDetailsProps> = ({
55
62
  toolInput,
56
63
  planContent,
57
64
  warning,
58
- isExpanded = false,
65
+ maxHeight,
59
66
  }) => {
60
67
  const startLineNumber =
61
68
  (toolInput?.startLineNumber as number | undefined) ??
62
69
  (toolName === WRITE_TOOL_NAME ? 1 : undefined);
63
70
 
64
- const content = (
71
+ const headerRows = 2 + (warning ? 1 : 0);
72
+
73
+ // Number of content rows rendered at once. The header stays fixed above the
74
+ // scrollable area; rows are budgeted for the scroll indicators and hint.
75
+ const visibleCount = Math.max(
76
+ 1,
77
+ (maxHeight ?? 24) -
78
+ headerRows -
79
+ SCROLL_INDICATOR_BUDGET -
80
+ SCROLL_HINT_BUDGET,
81
+ );
82
+
83
+ // Content linearized into rows so the details area can be scrolled with
84
+ // PgUp/PgDn while the confirmation options stay fixed at the bottom.
85
+ const diffLines = buildDiffLinesFromParams({
86
+ toolName,
87
+ parameters: JSON.stringify(toolInput),
88
+ startLineNumber,
89
+ });
90
+
91
+ const showToolInput =
92
+ toolName !== WRITE_TOOL_NAME &&
93
+ toolName !== EDIT_TOOL_NAME &&
94
+ toolName !== EXIT_PLAN_MODE_TOOL_NAME &&
95
+ toolName !== ENTER_PLAN_MODE_TOOL_NAME &&
96
+ toolName !== ASK_USER_QUESTION_TOOL_NAME &&
97
+ toolName !== BASH_TOOL_NAME &&
98
+ !!toolInput;
99
+
100
+ const jsonLines = showToolInput
101
+ ? highlightToAnsi(JSON.stringify(toolInput, null, 2), "json")
102
+ .split("\n")
103
+ .map((line, index) => (
104
+ <Box
105
+ key={`json-${index}`}
106
+ paddingLeft={2}
107
+ borderLeft
108
+ borderColor="cyan"
109
+ >
110
+ <Text>{line || " "}</Text>
111
+ </Box>
112
+ ))
113
+ : [];
114
+
115
+ const showPlan =
116
+ toolName !== ASK_USER_QUESTION_TOOL_NAME &&
117
+ toolName === EXIT_PLAN_MODE_TOOL_NAME &&
118
+ !!planContent;
119
+
120
+ const planLines = showPlan
121
+ ? planContent!.split("\n").map((line, index) => (
122
+ <Box key={`plan-${index}`}>
123
+ <Text>{line || " "}</Text>
124
+ </Box>
125
+ ))
126
+ : [];
127
+
128
+ const contentLines: React.ReactNode[] = [
129
+ ...diffLines,
130
+ ...jsonLines,
131
+ ...planLines,
132
+ ];
133
+ const totalLines = contentLines.length;
134
+ const maxScroll = Math.max(0, totalLines - visibleCount);
135
+
136
+ const [scrollOffset, setScrollOffset] = useState(0);
137
+
138
+ // Reset scrolling when a new confirmation appears.
139
+ useEffect(() => {
140
+ setScrollOffset(0);
141
+ }, [toolName, toolInput, planContent]);
142
+
143
+ const halfPage = Math.max(1, Math.ceil(visibleCount / 2));
144
+ useInput((input, key) => {
145
+ if (key.pageUp) {
146
+ setScrollOffset((offset) => Math.max(0, offset - visibleCount));
147
+ }
148
+ if (key.pageDown) {
149
+ setScrollOffset((offset) => Math.min(maxScroll, offset + visibleCount));
150
+ }
151
+ // Ctrl+u/Ctrl+d scroll half a page — fallback for keyboards without
152
+ // PgUp/PgDn (aligned with Claude Code's modal pager keys).
153
+ if (key.ctrl && input === "u") {
154
+ setScrollOffset((offset) => Math.max(0, offset - halfPage));
155
+ }
156
+ if (key.ctrl && input === "d") {
157
+ setScrollOffset((offset) => Math.min(maxScroll, offset + halfPage));
158
+ }
159
+ });
160
+
161
+ const clampedOffset = Math.min(scrollOffset, maxScroll);
162
+ const visibleLines = contentLines.slice(
163
+ clampedOffset,
164
+ clampedOffset + visibleCount,
165
+ );
166
+ const hasMoreAbove = clampedOffset > 0;
167
+ const hasMoreBelow = clampedOffset + visibleCount < totalLines;
168
+
169
+ return (
65
170
  <Box
66
171
  flexDirection="column"
172
+ flexShrink={0}
67
173
  borderStyle="single"
68
174
  borderColor="yellow"
69
175
  borderBottom={false}
70
176
  borderLeft={false}
71
177
  borderRight={false}
72
178
  >
73
- <Text color="yellow" bold>
179
+ <Text color="yellow" bold wrap="truncate-end">
74
180
  Tool: {toolName}
75
181
  </Text>
76
- <Text color="yellow">{getActionDescription(toolName, toolInput)}</Text>
77
- {warning && <Text color="red">⚠ {warning}</Text>}
78
-
79
- <DiffDisplay
80
- toolName={toolName}
81
- parameters={JSON.stringify(toolInput)}
82
- startLineNumber={startLineNumber}
83
- />
84
-
85
- {toolName !== WRITE_TOOL_NAME &&
86
- toolName !== EDIT_TOOL_NAME &&
87
- toolName !== EXIT_PLAN_MODE_TOOL_NAME &&
88
- toolName !== ENTER_PLAN_MODE_TOOL_NAME &&
89
- toolName !== ASK_USER_QUESTION_TOOL_NAME &&
90
- toolName !== BASH_TOOL_NAME &&
91
- !!toolInput && (
92
- <Box paddingLeft={2} borderLeft borderColor="cyan">
93
- <Text>
94
- {highlightToAnsi(JSON.stringify(toolInput, null, 2), "json")}
95
- </Text>
96
- </Box>
97
- )}
182
+ <Text color="yellow" wrap="truncate-end">
183
+ {getActionDescription(toolName, toolInput)}
184
+ </Text>
185
+ {warning && (
186
+ <Text color="red" wrap="truncate-end">
187
+ {warning}
188
+ </Text>
189
+ )}
98
190
 
99
- {toolName !== ASK_USER_QUESTION_TOOL_NAME &&
100
- toolName === EXIT_PLAN_MODE_TOOL_NAME &&
101
- !!planContent && (
102
- <PlanDisplay plan={planContent} isExpanded={isExpanded} />
103
- )}
191
+ {hasMoreAbove && (
192
+ <Box>
193
+ <Text color="gray" dimColor>
194
+ {clampedOffset} more
195
+ </Text>
196
+ </Box>
197
+ )}
198
+ {visibleLines}
199
+ {hasMoreBelow && (
200
+ <Box>
201
+ <Text color="gray" dimColor>
202
+ ↓ {totalLines - clampedOffset - visibleCount} more
203
+ </Text>
204
+ </Box>
205
+ )}
206
+ {(hasMoreAbove || hasMoreBelow) && (
207
+ <Box>
208
+ <Text color="gray" dimColor>
209
+ PgUp/PgDn page • Ctrl+u/d half page
210
+ </Text>
211
+ </Box>
212
+ )}
104
213
  </Box>
105
214
  );
106
-
107
- return content;
108
215
  };
109
216
 
110
217
  ConfirmationDetails.displayName = "ConfirmationDetails";