wave-code 0.7.0 → 0.7.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 (63) hide show
  1. package/dist/components/BackgroundTaskManager.d.ts.map +1 -1
  2. package/dist/components/BackgroundTaskManager.js +20 -12
  3. package/dist/components/ChatInterface.d.ts.map +1 -1
  4. package/dist/components/ChatInterface.js +28 -15
  5. package/dist/components/ConfirmationDetails.d.ts +1 -0
  6. package/dist/components/ConfirmationDetails.d.ts.map +1 -1
  7. package/dist/components/ConfirmationDetails.js +6 -9
  8. package/dist/components/ConfirmationSelector.d.ts +1 -0
  9. package/dist/components/ConfirmationSelector.d.ts.map +1 -1
  10. package/dist/components/ConfirmationSelector.js +164 -117
  11. package/dist/components/DiffDisplay.d.ts +1 -0
  12. package/dist/components/DiffDisplay.d.ts.map +1 -1
  13. package/dist/components/DiffDisplay.js +92 -29
  14. package/dist/components/HistorySearch.d.ts.map +1 -1
  15. package/dist/components/HistorySearch.js +26 -20
  16. package/dist/components/Markdown.d.ts.map +1 -1
  17. package/dist/components/Markdown.js +3 -1
  18. package/dist/components/McpManager.d.ts.map +1 -1
  19. package/dist/components/McpManager.js +49 -52
  20. package/dist/components/MessageBlockItem.d.ts +9 -0
  21. package/dist/components/MessageBlockItem.d.ts.map +1 -0
  22. package/dist/components/MessageBlockItem.js +11 -0
  23. package/dist/components/MessageList.d.ts +2 -4
  24. package/dist/components/MessageList.d.ts.map +1 -1
  25. package/dist/components/MessageList.js +28 -23
  26. package/dist/components/PluginDetail.d.ts.map +1 -1
  27. package/dist/components/PluginDetail.js +19 -22
  28. package/dist/components/SessionSelector.d.ts.map +1 -1
  29. package/dist/components/SessionSelector.js +8 -5
  30. package/dist/components/TaskList.d.ts.map +1 -1
  31. package/dist/components/TaskList.js +2 -5
  32. package/dist/components/ToolDisplay.d.ts.map +1 -1
  33. package/dist/components/ToolDisplay.js +1 -1
  34. package/dist/contexts/useChat.d.ts.map +1 -1
  35. package/dist/contexts/useChat.js +17 -2
  36. package/dist/utils/highlightUtils.d.ts +2 -0
  37. package/dist/utils/highlightUtils.d.ts.map +1 -0
  38. package/dist/utils/highlightUtils.js +69 -0
  39. package/dist/utils/toolParameterTransforms.d.ts +1 -1
  40. package/dist/utils/toolParameterTransforms.d.ts.map +1 -1
  41. package/dist/utils/toolParameterTransforms.js +10 -3
  42. package/package.json +4 -2
  43. package/src/components/BackgroundTaskManager.tsx +20 -12
  44. package/src/components/ChatInterface.tsx +35 -23
  45. package/src/components/ConfirmationDetails.tsx +13 -9
  46. package/src/components/ConfirmationSelector.tsx +207 -128
  47. package/src/components/DiffDisplay.tsx +162 -59
  48. package/src/components/HistorySearch.tsx +31 -25
  49. package/src/components/Markdown.tsx +3 -1
  50. package/src/components/McpManager.tsx +51 -59
  51. package/src/components/MessageBlockItem.tsx +83 -0
  52. package/src/components/MessageList.tsx +55 -52
  53. package/src/components/PluginDetail.tsx +30 -31
  54. package/src/components/SessionSelector.tsx +8 -5
  55. package/src/components/TaskList.tsx +2 -5
  56. package/src/components/ToolDisplay.tsx +5 -1
  57. package/src/contexts/useChat.tsx +18 -2
  58. package/src/utils/highlightUtils.ts +76 -0
  59. package/src/utils/toolParameterTransforms.ts +11 -2
  60. package/dist/components/MessageItem.d.ts +0 -8
  61. package/dist/components/MessageItem.d.ts.map +0 -1
  62. package/dist/components/MessageItem.js +0 -13
  63. package/src/components/MessageItem.tsx +0 -81
@@ -1 +1 @@
1
- {"version":3,"file":"BackgroundTaskManager.d.ts","sourceRoot":"","sources":["../../src/components/BackgroundTaskManager.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAcnD,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,0BAA0B,CA8TtE,CAAC"}
1
+ {"version":3,"file":"BackgroundTaskManager.d.ts","sourceRoot":"","sources":["../../src/components/BackgroundTaskManager.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAcnD,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,0BAA0B,CAsUtE,CAAC"}
@@ -51,11 +51,14 @@ export const BackgroundTaskManager = ({ onCancel, }) => {
51
51
  if (viewMode === "list") {
52
52
  // List mode navigation
53
53
  if (key.return) {
54
- if (tasks.length > 0 && selectedIndex < tasks.length) {
55
- const selectedTask = tasks[selectedIndex];
56
- setDetailTaskId(selectedTask.id);
57
- setViewMode("detail");
58
- }
54
+ setSelectedIndex((prev) => {
55
+ if (tasks.length > 0 && prev < tasks.length) {
56
+ const selectedTask = tasks[prev];
57
+ setDetailTaskId(selectedTask.id);
58
+ setViewMode("detail");
59
+ }
60
+ return prev;
61
+ });
59
62
  return;
60
63
  }
61
64
  if (key.escape) {
@@ -63,18 +66,23 @@ export const BackgroundTaskManager = ({ onCancel, }) => {
63
66
  return;
64
67
  }
65
68
  if (key.upArrow) {
66
- setSelectedIndex(Math.max(0, selectedIndex - 1));
69
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
67
70
  return;
68
71
  }
69
72
  if (key.downArrow) {
70
- setSelectedIndex(Math.min(tasks.length - 1, selectedIndex + 1));
73
+ setSelectedIndex((prev) => Math.min(tasks.length - 1, prev + 1));
71
74
  return;
72
75
  }
73
- if (input === "k" && tasks.length > 0 && selectedIndex < tasks.length) {
74
- const selectedTask = tasks[selectedIndex];
75
- if (selectedTask.status === "running") {
76
- stopTask(selectedTask.id);
77
- }
76
+ if (input === "k") {
77
+ setSelectedIndex((prev) => {
78
+ if (tasks.length > 0 && prev < tasks.length) {
79
+ const selectedTask = tasks[prev];
80
+ if (selectedTask.status === "running") {
81
+ stopTask(selectedTask.id);
82
+ }
83
+ }
84
+ return prev;
85
+ });
78
86
  return;
79
87
  }
80
88
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ChatInterface.d.ts","sourceRoot":"","sources":["../../src/components/ChatInterface.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAgC,MAAM,OAAO,CAAC;AAYrD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAyHjC,CAAC"}
1
+ {"version":3,"file":"ChatInterface.d.ts","sourceRoot":"","sources":["../../src/components/ChatInterface.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAYtE,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAqIjC,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState, useCallback } from "react";
2
+ import { useState, useCallback, useLayoutEffect } from "react";
3
3
  import { Box, useStdout } from "ink";
4
4
  import { MessageList } from "./MessageList.js";
5
5
  import { InputBox } from "./InputBox.js";
@@ -10,38 +10,51 @@ import { ConfirmationSelector } from "./ConfirmationSelector.js";
10
10
  import { useChat } from "../contexts/useChat.js";
11
11
  export const ChatInterface = () => {
12
12
  const { stdout } = useStdout();
13
- const [isDetailsTooTall, setIsDetailsTooTall] = useState(false);
13
+ const [detailsHeight, setDetailsHeight] = useState(0);
14
+ const [selectorHeight, setSelectorHeight] = useState(0);
15
+ const [isConfirmationTooTall, setIsConfirmationTooTall] = useState(false);
14
16
  const { messages, isLoading, isCommandRunning, isCompressing, sendMessage, abortMessage, mcpServers, connectMcpServer, disconnectMcpServer, isExpanded, sessionId, latestTotalTokens, slashCommands, hasSlashCommand, isConfirmationVisible, confirmingTool, handleConfirmationDecision, handleConfirmationCancel: originalHandleConfirmationCancel, setWasLastDetailsTooTall, } = useChat();
15
- const handleHeightMeasured = useCallback((height) => {
17
+ const handleDetailsHeightMeasured = useCallback((height) => {
18
+ setDetailsHeight(height);
19
+ }, []);
20
+ const handleSelectorHeightMeasured = useCallback((height) => {
21
+ setSelectorHeight(height);
22
+ }, []);
23
+ useLayoutEffect(() => {
16
24
  const terminalHeight = stdout?.rows || 24;
17
- if (height > terminalHeight - 10) {
18
- setIsDetailsTooTall(true);
25
+ const totalHeight = detailsHeight + selectorHeight;
26
+ if (totalHeight > terminalHeight) {
27
+ setIsConfirmationTooTall(true);
19
28
  }
20
29
  else {
21
- setIsDetailsTooTall(false);
30
+ setIsConfirmationTooTall(false);
22
31
  }
23
- }, [stdout?.rows]);
32
+ }, [detailsHeight, selectorHeight, stdout?.rows]);
24
33
  const handleConfirmationCancel = useCallback(() => {
25
- if (isDetailsTooTall) {
34
+ if (isConfirmationTooTall) {
26
35
  setWasLastDetailsTooTall((prev) => prev + 1);
27
- setIsDetailsTooTall(false);
36
+ setIsConfirmationTooTall(false);
28
37
  }
29
38
  originalHandleConfirmationCancel();
30
39
  }, [
31
- isDetailsTooTall,
40
+ isConfirmationTooTall,
32
41
  originalHandleConfirmationCancel,
33
42
  setWasLastDetailsTooTall,
34
43
  ]);
35
44
  const wrappedHandleConfirmationDecision = useCallback((decision) => {
36
- if (isDetailsTooTall) {
45
+ if (isConfirmationTooTall) {
37
46
  setWasLastDetailsTooTall((prev) => prev + 1);
38
- setIsDetailsTooTall(false);
47
+ setIsConfirmationTooTall(false);
39
48
  }
40
49
  handleConfirmationDecision(decision);
41
- }, [isDetailsTooTall, handleConfirmationDecision, setWasLastDetailsTooTall]);
50
+ }, [
51
+ isConfirmationTooTall,
52
+ handleConfirmationDecision,
53
+ setWasLastDetailsTooTall,
54
+ ]);
42
55
  if (!sessionId)
43
56
  return null;
44
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(MessageList, { messages: messages, isLoading: isLoading, isCommandRunning: isCommandRunning, isExpanded: isExpanded, forceStaticLastMessage: isDetailsTooTall }), (isLoading || isCommandRunning || isCompressing) &&
57
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(MessageList, { messages: messages, isExpanded: isExpanded, hideDynamicBlocks: isConfirmationVisible }), (isLoading || isCommandRunning || isCompressing) &&
45
58
  !isConfirmationVisible &&
46
- !isExpanded && (_jsx(LoadingIndicator, { isLoading: isLoading, isCommandRunning: isCommandRunning, isCompressing: isCompressing, latestTotalTokens: latestTotalTokens })), !isConfirmationVisible && !isExpanded && _jsx(TaskList, {}), isConfirmationVisible && (_jsxs(_Fragment, { children: [_jsx(ConfirmationDetails, { toolName: confirmingTool.name, toolInput: confirmingTool.input, isExpanded: isExpanded, onHeightMeasured: handleHeightMeasured }), _jsx(ConfirmationSelector, { toolName: confirmingTool.name, toolInput: confirmingTool.input, suggestedPrefix: confirmingTool.suggestedPrefix, hidePersistentOption: confirmingTool.hidePersistentOption, isExpanded: isExpanded, onDecision: wrappedHandleConfirmationDecision, onCancel: handleConfirmationCancel, onAbort: abortMessage })] })), !isConfirmationVisible && !isExpanded && (_jsx(InputBox, { isLoading: isLoading, isCommandRunning: isCommandRunning, sendMessage: sendMessage, abortMessage: abortMessage, mcpServers: mcpServers, connectMcpServer: connectMcpServer, disconnectMcpServer: disconnectMcpServer, slashCommands: slashCommands, hasSlashCommand: hasSlashCommand }))] }));
59
+ !isExpanded && (_jsx(LoadingIndicator, { isLoading: isLoading, isCommandRunning: isCommandRunning, isCompressing: isCompressing, latestTotalTokens: latestTotalTokens })), !isConfirmationVisible && !isExpanded && _jsx(TaskList, {}), isConfirmationVisible && (_jsxs(_Fragment, { children: [_jsx(ConfirmationDetails, { toolName: confirmingTool.name, toolInput: confirmingTool.input, isExpanded: isExpanded, onHeightMeasured: handleDetailsHeightMeasured, isStatic: isConfirmationTooTall }), _jsx(ConfirmationSelector, { toolName: confirmingTool.name, toolInput: confirmingTool.input, suggestedPrefix: confirmingTool.suggestedPrefix, hidePersistentOption: confirmingTool.hidePersistentOption, isExpanded: isExpanded, onDecision: wrappedHandleConfirmationDecision, onCancel: handleConfirmationCancel, onAbort: abortMessage, onHeightMeasured: handleSelectorHeightMeasured })] })), !isConfirmationVisible && !isExpanded && (_jsx(InputBox, { isLoading: isLoading, isCommandRunning: isCommandRunning, sendMessage: sendMessage, abortMessage: abortMessage, mcpServers: mcpServers, connectMcpServer: connectMcpServer, disconnectMcpServer: disconnectMcpServer, slashCommands: slashCommands, hasSlashCommand: hasSlashCommand }))] }));
47
60
  };
@@ -4,6 +4,7 @@ export interface ConfirmationDetailsProps {
4
4
  toolInput?: Record<string, unknown>;
5
5
  isExpanded?: boolean;
6
6
  onHeightMeasured?: (height: number) => void;
7
+ isStatic?: boolean;
7
8
  }
8
9
  export declare const ConfirmationDetails: React.FC<ConfirmationDetailsProps>;
9
10
  //# sourceMappingURL=ConfirmationDetails.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ConfirmationDetails.d.ts","sourceRoot":"","sources":["../../src/components/ConfirmationDetails.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4C,MAAM,OAAO,CAAC;AAqCjE,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7C;AAED,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CAuDlE,CAAC"}
1
+ {"version":3,"file":"ConfirmationDetails.d.ts","sourceRoot":"","sources":["../../src/components/ConfirmationDetails.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkC,MAAM,OAAO,CAAC;AAqCvD,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CA0DlE,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- import { useLayoutEffect, useRef, useState } from "react";
2
+ import { useLayoutEffect, useRef } from "react";
3
3
  import { Box, Text, useStdout, measureElement, Static } from "ink";
4
4
  import { BASH_TOOL_NAME, EDIT_TOOL_NAME, WRITE_TOOL_NAME, EXIT_PLAN_MODE_TOOL_NAME, ASK_USER_QUESTION_TOOL_NAME, } from "wave-agent-sdk";
5
5
  import { DiffDisplay } from "./DiffDisplay.js";
@@ -24,21 +24,18 @@ const getActionDescription = (toolName, toolInput) => {
24
24
  return "Execute operation";
25
25
  }
26
26
  };
27
- export const ConfirmationDetails = ({ toolName, toolInput, isExpanded = false, onHeightMeasured, }) => {
27
+ export const ConfirmationDetails = ({ toolName, toolInput, isExpanded = false, onHeightMeasured, isStatic = false, }) => {
28
28
  const { stdout } = useStdout();
29
- const [isStatic, setIsStatic] = useState(false);
30
29
  const boxRef = useRef(null);
30
+ const startLineNumber = toolInput?.startLineNumber ??
31
+ (toolName === WRITE_TOOL_NAME ? 1 : undefined);
31
32
  useLayoutEffect(() => {
32
33
  if (boxRef.current) {
33
34
  const { height } = measureElement(boxRef.current);
34
- const terminalHeight = stdout?.rows || 24;
35
- if (height > terminalHeight - 10) {
36
- setIsStatic(true);
37
- }
38
35
  onHeightMeasured?.(height);
39
36
  }
40
- }, [stdout?.rows, onHeightMeasured]);
41
- const content = (_jsxs(Box, { ref: boxRef, flexDirection: "column", borderStyle: "single", borderColor: "yellow", borderBottom: false, borderLeft: false, borderRight: false, paddingTop: 1, children: [_jsxs(Text, { color: "yellow", bold: true, children: ["Tool: ", toolName] }), _jsx(Text, { color: "yellow", children: getActionDescription(toolName, toolInput) }), _jsx(DiffDisplay, { toolName: toolName, parameters: JSON.stringify(toolInput) }), toolName !== ASK_USER_QUESTION_TOOL_NAME &&
37
+ }, [stdout?.rows, onHeightMeasured, toolInput, isExpanded]);
38
+ const content = (_jsxs(Box, { ref: boxRef, flexDirection: "column", borderStyle: "single", borderColor: "yellow", borderBottom: false, borderLeft: false, borderRight: false, children: [_jsxs(Text, { color: "yellow", bold: true, children: ["Tool: ", toolName] }), _jsx(Text, { color: "yellow", children: getActionDescription(toolName, toolInput) }), _jsx(DiffDisplay, { toolName: toolName, parameters: JSON.stringify(toolInput), startLineNumber: startLineNumber }), toolName !== ASK_USER_QUESTION_TOOL_NAME &&
42
39
  toolName === EXIT_PLAN_MODE_TOOL_NAME &&
43
40
  !!toolInput?.plan_content && (_jsx(PlanDisplay, { plan: toolInput.plan_content, isExpanded: isExpanded }))] }));
44
41
  if (isStatic) {
@@ -9,6 +9,7 @@ export interface ConfirmationSelectorProps {
9
9
  onDecision: (decision: PermissionDecision) => void;
10
10
  onCancel: () => void;
11
11
  onAbort: () => void;
12
+ onHeightMeasured?: (height: number) => void;
12
13
  }
13
14
  export declare const ConfirmationSelector: React.FC<ConfirmationSelectorProps>;
14
15
  //# sourceMappingURL=ConfirmationSelector.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ConfirmationSelector.d.ts","sourceRoot":"","sources":["../../src/components/ConfirmationSelector.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAExC,OAAO,KAAK,EAAE,kBAAkB,EAAwB,MAAM,gBAAgB,CAAC;AAgB/E,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACnD,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AASD,eAAO,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CAwapE,CAAC"}
1
+ {"version":3,"file":"ConfirmationSelector.d.ts","sourceRoot":"","sources":["../../src/components/ConfirmationSelector.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4C,MAAM,OAAO,CAAC;AAEjE,OAAO,KAAK,EAAE,kBAAkB,EAAwB,MAAM,gBAAgB,CAAC;AAgB/E,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACnD,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7C;AASD,eAAO,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CAsfpE,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useState } from "react";
3
- import { Box, Text, useInput } from "ink";
2
+ import { useLayoutEffect, useRef, useState } from "react";
3
+ import { Box, Text, useInput, useStdout, measureElement } from "ink";
4
4
  import { BASH_TOOL_NAME, EXIT_PLAN_MODE_TOOL_NAME, ASK_USER_QUESTION_TOOL_NAME, } from "wave-agent-sdk";
5
5
  const getHeaderColor = (header) => {
6
6
  const colors = ["red", "green", "blue", "magenta", "cyan"];
@@ -10,24 +10,34 @@ const getHeaderColor = (header) => {
10
10
  }
11
11
  return colors[Math.abs(hash) % colors.length];
12
12
  };
13
- export const ConfirmationSelector = ({ toolName, toolInput, suggestedPrefix, hidePersistentOption, isExpanded = false, onDecision, onCancel, onAbort, }) => {
13
+ export const ConfirmationSelector = ({ toolName, toolInput, suggestedPrefix, hidePersistentOption, isExpanded = false, onDecision, onCancel, onAbort, onHeightMeasured, }) => {
14
+ const { stdout } = useStdout();
15
+ const boxRef = useRef(null);
16
+ useLayoutEffect(() => {
17
+ if (boxRef.current) {
18
+ const { height } = measureElement(boxRef.current);
19
+ onHeightMeasured?.(height);
20
+ }
21
+ }, [stdout?.rows, onHeightMeasured, toolName, toolInput, isExpanded]);
14
22
  const [state, setState] = useState({
15
- selectedOption: "allow",
23
+ selectedOption: toolName === EXIT_PLAN_MODE_TOOL_NAME ? "clear" : "allow",
16
24
  alternativeText: "",
17
25
  alternativeCursorPosition: 0,
18
26
  hasUserInput: false,
19
27
  });
20
- const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
21
- const [selectedOptionIndex, setSelectedOptionIndex] = useState(0);
22
- const [selectedOptionIndices, setSelectedOptionIndices] = useState(new Set());
23
- const [userAnswers, setUserAnswers] = useState({});
24
- const [otherText, setOtherText] = useState("");
25
- const [otherCursorPosition, setOtherCursorPosition] = useState(0);
28
+ const [questionState, setQuestionState] = useState({
29
+ currentQuestionIndex: 0,
30
+ selectedOptionIndex: 0,
31
+ selectedOptionIndices: new Set(),
32
+ userAnswers: {},
33
+ otherText: "",
34
+ otherCursorPosition: 0,
35
+ });
26
36
  const questions = toolInput?.questions || [];
27
- const currentQuestion = questions[currentQuestionIndex];
37
+ const currentQuestion = questions[questionState.currentQuestionIndex];
28
38
  const getAutoOptionText = () => {
29
39
  if (toolName === EXIT_PLAN_MODE_TOOL_NAME) {
30
- return "Yes, and auto-accept edits";
40
+ return "Yes, auto-accept edits";
31
41
  }
32
42
  if (toolName === BASH_TOOL_NAME) {
33
43
  if (suggestedPrefix) {
@@ -48,104 +58,140 @@ export const ConfirmationSelector = ({ toolName, toolInput, suggestedPrefix, hid
48
58
  return;
49
59
  const options = [...currentQuestion.options, { label: "Other" }];
50
60
  const isMultiSelect = currentQuestion.multiSelect;
51
- const isOtherFocused = selectedOptionIndex === options.length - 1;
52
61
  if (key.return) {
53
- let answer = "";
54
- if (isMultiSelect) {
55
- const selectedLabels = Array.from(selectedOptionIndices)
56
- .filter((i) => i < currentQuestion.options.length)
57
- .map((i) => currentQuestion.options[i].label);
58
- const isOtherChecked = selectedOptionIndices.has(options.length - 1);
59
- if (isOtherChecked && otherText.trim()) {
60
- selectedLabels.push(otherText.trim());
62
+ setQuestionState((prev) => {
63
+ const isOtherFocused = prev.selectedOptionIndex === options.length - 1;
64
+ let answer = "";
65
+ if (isMultiSelect) {
66
+ const selectedLabels = Array.from(prev.selectedOptionIndices)
67
+ .filter((i) => i < currentQuestion.options.length)
68
+ .map((i) => currentQuestion.options[i].label);
69
+ const isOtherChecked = prev.selectedOptionIndices.has(options.length - 1);
70
+ if (isOtherChecked && prev.otherText.trim()) {
71
+ selectedLabels.push(prev.otherText.trim());
72
+ }
73
+ answer = selectedLabels.join(", ");
61
74
  }
62
- answer = selectedLabels.join(", ");
63
- }
64
- else {
65
- if (isOtherFocused) {
66
- answer = otherText.trim();
75
+ else {
76
+ if (isOtherFocused) {
77
+ answer = prev.otherText.trim();
78
+ }
79
+ else {
80
+ answer = options[prev.selectedOptionIndex].label;
81
+ }
82
+ }
83
+ if (!answer)
84
+ return prev;
85
+ const newAnswers = {
86
+ ...prev.userAnswers,
87
+ [currentQuestion.question]: answer,
88
+ };
89
+ if (prev.currentQuestionIndex < questions.length - 1) {
90
+ return {
91
+ ...prev,
92
+ currentQuestionIndex: prev.currentQuestionIndex + 1,
93
+ selectedOptionIndex: 0,
94
+ selectedOptionIndices: new Set(),
95
+ userAnswers: newAnswers,
96
+ otherText: "",
97
+ otherCursorPosition: 0,
98
+ };
67
99
  }
68
100
  else {
69
- answer = options[selectedOptionIndex].label;
101
+ onDecision({
102
+ behavior: "allow",
103
+ message: JSON.stringify(newAnswers),
104
+ });
105
+ return {
106
+ ...prev,
107
+ userAnswers: newAnswers,
108
+ };
70
109
  }
71
- }
72
- if (!answer)
73
- return;
74
- const newAnswers = {
75
- ...userAnswers,
76
- [currentQuestion.question]: answer,
77
- };
78
- setUserAnswers(newAnswers);
79
- if (currentQuestionIndex < questions.length - 1) {
80
- setCurrentQuestionIndex(currentQuestionIndex + 1);
81
- setSelectedOptionIndex(0);
82
- setSelectedOptionIndices(new Set());
83
- setOtherText("");
84
- setOtherCursorPosition(0);
85
- }
86
- else {
87
- onDecision({
88
- behavior: "allow",
89
- message: JSON.stringify(newAnswers),
90
- });
91
- }
110
+ });
92
111
  return;
93
112
  }
94
113
  if (input === " ") {
95
- if (isMultiSelect &&
96
- (!isOtherFocused || !selectedOptionIndices.has(selectedOptionIndex))) {
97
- setSelectedOptionIndices((prev) => {
98
- const next = new Set(prev);
99
- if (next.has(selectedOptionIndex))
100
- next.delete(selectedOptionIndex);
114
+ setQuestionState((prev) => {
115
+ const isOtherFocused = prev.selectedOptionIndex === options.length - 1;
116
+ if (isMultiSelect &&
117
+ (!isOtherFocused ||
118
+ !prev.selectedOptionIndices.has(prev.selectedOptionIndex))) {
119
+ const nextIndices = new Set(prev.selectedOptionIndices);
120
+ if (nextIndices.has(prev.selectedOptionIndex))
121
+ nextIndices.delete(prev.selectedOptionIndex);
101
122
  else
102
- next.add(selectedOptionIndex);
103
- return next;
104
- });
105
- return;
106
- }
107
- if (!isOtherFocused)
108
- return;
123
+ nextIndices.add(prev.selectedOptionIndex);
124
+ return {
125
+ ...prev,
126
+ selectedOptionIndices: nextIndices,
127
+ };
128
+ }
129
+ return prev;
130
+ });
131
+ // If it's other and focused, we don't return here, allowing the input handler below to handle it
109
132
  }
110
133
  if (key.upArrow) {
111
- if (selectedOptionIndex > 0)
112
- setSelectedOptionIndex(selectedOptionIndex - 1);
134
+ setQuestionState((prev) => ({
135
+ ...prev,
136
+ selectedOptionIndex: Math.max(0, prev.selectedOptionIndex - 1),
137
+ }));
113
138
  return;
114
139
  }
115
140
  if (key.downArrow) {
116
- if (selectedOptionIndex < options.length - 1)
117
- setSelectedOptionIndex(selectedOptionIndex + 1);
141
+ setQuestionState((prev) => ({
142
+ ...prev,
143
+ selectedOptionIndex: Math.min(options.length - 1, prev.selectedOptionIndex + 1),
144
+ }));
118
145
  return;
119
146
  }
120
- if (isOtherFocused) {
121
- if (key.leftArrow) {
122
- setOtherCursorPosition((prev) => Math.max(0, prev - 1));
123
- return;
124
- }
125
- if (key.rightArrow) {
126
- setOtherCursorPosition((prev) => Math.min(otherText.length, prev + 1));
127
- return;
128
- }
129
- if (key.backspace || key.delete) {
130
- if (otherCursorPosition > 0) {
131
- setOtherText((prev) => prev.slice(0, otherCursorPosition - 1) +
132
- prev.slice(otherCursorPosition));
133
- setOtherCursorPosition((prev) => prev - 1);
147
+ setQuestionState((prev) => {
148
+ const isOtherFocused = prev.selectedOptionIndex === options.length - 1;
149
+ if (isOtherFocused) {
150
+ if (key.leftArrow) {
151
+ return {
152
+ ...prev,
153
+ otherCursorPosition: Math.max(0, prev.otherCursorPosition - 1),
154
+ };
155
+ }
156
+ if (key.rightArrow) {
157
+ return {
158
+ ...prev,
159
+ otherCursorPosition: Math.min(prev.otherText.length, prev.otherCursorPosition + 1),
160
+ };
161
+ }
162
+ if (key.backspace || key.delete) {
163
+ if (prev.otherCursorPosition > 0) {
164
+ return {
165
+ ...prev,
166
+ otherText: prev.otherText.slice(0, prev.otherCursorPosition - 1) +
167
+ prev.otherText.slice(prev.otherCursorPosition),
168
+ otherCursorPosition: prev.otherCursorPosition - 1,
169
+ };
170
+ }
171
+ }
172
+ if (input && !key.ctrl && !key.meta) {
173
+ return {
174
+ ...prev,
175
+ otherText: prev.otherText.slice(0, prev.otherCursorPosition) +
176
+ input +
177
+ prev.otherText.slice(prev.otherCursorPosition),
178
+ otherCursorPosition: prev.otherCursorPosition + input.length,
179
+ };
134
180
  }
135
- return;
136
- }
137
- if (input && !key.ctrl && !key.meta) {
138
- setOtherText((prev) => prev.slice(0, otherCursorPosition) +
139
- input +
140
- prev.slice(otherCursorPosition));
141
- setOtherCursorPosition((prev) => prev + input.length);
142
- return;
143
181
  }
144
- }
182
+ return prev;
183
+ });
145
184
  return;
146
185
  }
147
186
  if (key.return) {
148
- if (state.selectedOption === "allow") {
187
+ if (state.selectedOption === "clear") {
188
+ onDecision({
189
+ behavior: "allow",
190
+ newPermissionMode: "acceptEdits",
191
+ clearContext: true,
192
+ });
193
+ }
194
+ else if (state.selectedOption === "allow") {
149
195
  if (toolName === EXIT_PLAN_MODE_TOOL_NAME) {
150
196
  onDecision({ behavior: "allow", newPermissionMode: "default" });
151
197
  }
@@ -185,30 +231,31 @@ export const ConfirmationSelector = ({ toolName, toolInput, suggestedPrefix, hid
185
231
  return;
186
232
  }
187
233
  }
234
+ const availableOptions = [];
235
+ if (toolName === EXIT_PLAN_MODE_TOOL_NAME)
236
+ availableOptions.push("clear");
237
+ availableOptions.push("allow");
238
+ if (!hidePersistentOption)
239
+ availableOptions.push("auto");
240
+ availableOptions.push("alternative");
188
241
  if (key.upArrow) {
189
- setState((prev) => {
190
- if (prev.selectedOption === "alternative")
191
- return {
192
- ...prev,
193
- selectedOption: hidePersistentOption ? "allow" : "auto",
194
- };
195
- if (prev.selectedOption === "auto")
196
- return { ...prev, selectedOption: "allow" };
197
- return prev;
198
- });
242
+ const currentIndex = availableOptions.indexOf(state.selectedOption);
243
+ if (currentIndex > 0) {
244
+ setState((prev) => ({
245
+ ...prev,
246
+ selectedOption: availableOptions[currentIndex - 1],
247
+ }));
248
+ }
199
249
  return;
200
250
  }
201
251
  if (key.downArrow) {
202
- setState((prev) => {
203
- if (prev.selectedOption === "allow")
204
- return {
205
- ...prev,
206
- selectedOption: hidePersistentOption ? "alternative" : "auto",
207
- };
208
- if (prev.selectedOption === "auto")
209
- return { ...prev, selectedOption: "alternative" };
210
- return prev;
211
- });
252
+ const currentIndex = availableOptions.indexOf(state.selectedOption);
253
+ if (currentIndex < availableOptions.length - 1) {
254
+ setState((prev) => ({
255
+ ...prev,
256
+ selectedOption: availableOptions[currentIndex + 1],
257
+ }));
258
+ }
212
259
  return;
213
260
  }
214
261
  if (input && !key.ctrl && !key.meta && !("alt" in key && key.alt)) {
@@ -244,23 +291,23 @@ export const ConfirmationSelector = ({ toolName, toolInput, suggestedPrefix, hid
244
291
  return;
245
292
  }
246
293
  });
247
- const placeholderText = "Type here to tell Wave what to do differently";
294
+ const placeholderText = "Type here to tell Wave what to change";
248
295
  const showPlaceholder = state.selectedOption === "alternative" && !state.hasUserInput;
249
- return (_jsxs(Box, { flexDirection: "column", children: [toolName === ASK_USER_QUESTION_TOOL_NAME &&
296
+ return (_jsxs(Box, { ref: boxRef, flexDirection: "column", children: [toolName === ASK_USER_QUESTION_TOOL_NAME &&
250
297
  currentQuestion &&
251
298
  !isExpanded && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: getHeaderColor(currentQuestion.header), bold: true, children: currentQuestion.header.slice(0, 12).toUpperCase() }), _jsx(Box, { marginLeft: 1, children: _jsx(Text, { bold: true, children: currentQuestion.question }) })] }), _jsx(Box, { flexDirection: "column", children: [...currentQuestion.options, { label: "Other" }].map((option, index) => {
252
- const isSelected = selectedOptionIndex === index;
299
+ const isSelected = questionState.selectedOptionIndex === index;
253
300
  const isChecked = currentQuestion.multiSelect
254
- ? selectedOptionIndices.has(index)
301
+ ? questionState.selectedOptionIndices.has(index)
255
302
  : isSelected;
256
303
  const isOther = index === currentQuestion.options.length;
257
304
  return (_jsx(Box, { children: _jsxs(Text, { color: isSelected ? "black" : "white", backgroundColor: isSelected ? "yellow" : undefined, children: [isSelected ? "> " : " ", currentQuestion.multiSelect
258
305
  ? isChecked
259
306
  ? "[x] "
260
307
  : "[ ] "
261
- : "", option.label, option.description ? ` - ${option.description}` : "", isOther && isSelected && (_jsxs(Text, { children: [":", " ", otherText ? (_jsxs(_Fragment, { children: [otherText.slice(0, otherCursorPosition), _jsx(Text, { backgroundColor: "white", color: "black", children: otherText[otherCursorPosition] || " " }), otherText.slice(otherCursorPosition + 1)] })) : (_jsx(Text, { color: "gray", dimColor: true, children: "[Type your answer...]" }))] }))] }) }, index));
262
- }) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["Question ", currentQuestionIndex + 1, " of ", questions.length, " \u2022", currentQuestion.multiSelect ? " Space to toggle •" : "", " Use \u2191\u2193 to navigate \u2022 Enter to confirm"] }) })] })), toolName !== ASK_USER_QUESTION_TOOL_NAME && !isExpanded && (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, children: _jsx(Text, { children: "Do you want to proceed?" }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "allow" ? "black" : "white", backgroundColor: state.selectedOption === "allow" ? "yellow" : undefined, bold: state.selectedOption === "allow", children: [state.selectedOption === "allow" ? "> " : " ", toolName === EXIT_PLAN_MODE_TOOL_NAME
263
- ? "Yes, proceed with default mode"
264
- : "Yes"] }) }, "allow-option"), !hidePersistentOption && (_jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "auto" ? "black" : "white", backgroundColor: state.selectedOption === "auto" ? "yellow" : undefined, bold: state.selectedOption === "auto", children: [state.selectedOption === "auto" ? "> " : " ", getAutoOptionText()] }) }, "auto-option")), _jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "alternative" ? "black" : "white", backgroundColor: state.selectedOption === "alternative" ? "yellow" : undefined, bold: state.selectedOption === "alternative", children: [state.selectedOption === "alternative" ? "> " : " ", showPlaceholder ? (_jsx(Text, { color: "gray", dimColor: true, children: placeholderText })) : (_jsx(Text, { children: state.alternativeText ? (_jsxs(_Fragment, { children: [state.alternativeText.slice(0, state.alternativeCursorPosition), _jsx(Text, { backgroundColor: "white", color: "black", children: state.alternativeText[state.alternativeCursorPosition] || " " }), state.alternativeText.slice(state.alternativeCursorPosition + 1)] })) : ("Type here to tell Wave what to do differently") }))] }) }, "alternative-option")] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Use \u2191\u2193 to navigate \u2022 ESC to cancel" }) })] }))] }));
308
+ : "", option.label, option.description ? ` - ${option.description}` : "", isOther && isSelected && (_jsxs(Text, { children: [":", " ", questionState.otherText ? (_jsxs(_Fragment, { children: [questionState.otherText.slice(0, questionState.otherCursorPosition), _jsx(Text, { backgroundColor: "white", color: "black", children: questionState.otherText[questionState.otherCursorPosition] || " " }), questionState.otherText.slice(questionState.otherCursorPosition + 1)] })) : (_jsx(Text, { color: "gray", dimColor: true, children: "[Type your answer...]" }))] }))] }) }, index));
309
+ }) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["Question ", questionState.currentQuestionIndex + 1, " of", " ", questions.length, " \u2022", currentQuestion.multiSelect ? " Space to toggle •" : "", " Use \u2191\u2193 to navigate \u2022 Enter to confirm"] }) })] })), toolName !== ASK_USER_QUESTION_TOOL_NAME && !isExpanded && (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, children: _jsx(Text, { children: "Do you want to proceed?" }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [toolName === EXIT_PLAN_MODE_TOOL_NAME && (_jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "clear" ? "black" : "white", backgroundColor: state.selectedOption === "clear" ? "yellow" : undefined, bold: state.selectedOption === "clear", children: [state.selectedOption === "clear" ? "> " : " ", "Yes, clear context and auto-accept edits"] }) }, "clear-option")), _jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "allow" ? "black" : "white", backgroundColor: state.selectedOption === "allow" ? "yellow" : undefined, bold: state.selectedOption === "allow", children: [state.selectedOption === "allow" ? "> " : " ", toolName === EXIT_PLAN_MODE_TOOL_NAME
310
+ ? "Yes, manually approve edits"
311
+ : "Yes, proceed"] }) }, "allow-option"), !hidePersistentOption && (_jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "auto" ? "black" : "white", backgroundColor: state.selectedOption === "auto" ? "yellow" : undefined, bold: state.selectedOption === "auto", children: [state.selectedOption === "auto" ? "> " : " ", getAutoOptionText()] }) }, "auto-option")), _jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "alternative" ? "black" : "white", backgroundColor: state.selectedOption === "alternative" ? "yellow" : undefined, bold: state.selectedOption === "alternative", children: [state.selectedOption === "alternative" ? "> " : " ", showPlaceholder ? (_jsx(Text, { color: "gray", dimColor: true, children: placeholderText })) : (_jsx(Text, { children: state.alternativeText ? (_jsxs(_Fragment, { children: [state.alternativeText.slice(0, state.alternativeCursorPosition), _jsx(Text, { backgroundColor: "white", color: "black", children: state.alternativeText[state.alternativeCursorPosition] || " " }), state.alternativeText.slice(state.alternativeCursorPosition + 1)] })) : ("Type here to tell Wave what to change") }))] }) }, "alternative-option")] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Use \u2191\u2193 to navigate \u2022 ESC to cancel" }) })] }))] }));
265
312
  };
266
313
  ConfirmationSelector.displayName = "ConfirmationSelector";
@@ -2,6 +2,7 @@ import React from "react";
2
2
  interface DiffDisplayProps {
3
3
  toolName?: string;
4
4
  parameters?: string;
5
+ startLineNumber?: number;
5
6
  }
6
7
  export declare const DiffDisplay: React.FC<DiffDisplayProps>;
7
8
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"DiffDisplay.d.ts","sourceRoot":"","sources":["../../src/components/DiffDisplay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAMvC,UAAU,gBAAgB;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAwSlD,CAAC"}
1
+ {"version":3,"file":"DiffDisplay.d.ts","sourceRoot":"","sources":["../../src/components/DiffDisplay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAMvC,UAAU,gBAAgB;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CA8VlD,CAAC"}