wave-code 0.12.0 → 0.12.2
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.
- package/README.md +9 -51
- package/dist/acp/agent.js +1 -1
- package/dist/components/App.d.ts +1 -0
- package/dist/components/App.d.ts.map +1 -1
- package/dist/components/App.js +5 -2
- package/dist/components/BackgroundTaskManager.d.ts.map +1 -1
- package/dist/components/BackgroundTaskManager.js +2 -1
- package/dist/components/BangDisplay.d.ts.map +1 -1
- package/dist/components/BangDisplay.js +2 -14
- package/dist/components/ChatInterface.d.ts.map +1 -1
- package/dist/components/ChatInterface.js +17 -27
- package/dist/components/ConfirmationDetails.d.ts +0 -1
- package/dist/components/ConfirmationDetails.d.ts.map +1 -1
- package/dist/components/ConfirmationDetails.js +4 -12
- package/dist/components/ConfirmationSelector.d.ts +0 -1
- package/dist/components/ConfirmationSelector.d.ts.map +1 -1
- package/dist/components/ConfirmationSelector.js +4 -12
- package/dist/components/DiffDisplay.d.ts.map +1 -1
- package/dist/components/DiffDisplay.js +2 -23
- package/dist/components/MessageList.d.ts +1 -2
- package/dist/components/MessageList.d.ts.map +1 -1
- package/dist/components/MessageList.js +12 -17
- package/dist/components/RewindCommand.d.ts.map +1 -1
- package/dist/components/RewindCommand.js +2 -4
- package/dist/components/StatusCommand.d.ts.map +1 -1
- package/dist/components/StatusCommand.js +1 -1
- package/dist/components/ToolDisplay.d.ts.map +1 -1
- package/dist/components/ToolDisplay.js +3 -6
- package/dist/contexts/useChat.d.ts +0 -2
- package/dist/contexts/useChat.d.ts.map +1 -1
- package/dist/contexts/useChat.js +27 -44
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/utils/highlightUtils.d.ts.map +1 -1
- package/dist/utils/highlightUtils.js +0 -8
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +13 -11
- package/dist/utils/throttle.d.ts +15 -0
- package/dist/utils/throttle.d.ts.map +1 -0
- package/dist/utils/throttle.js +55 -0
- package/dist/utils/toolParameterTransforms.d.ts +5 -1
- package/dist/utils/toolParameterTransforms.d.ts.map +1 -1
- package/dist/utils/toolParameterTransforms.js +14 -0
- package/dist/utils/worktree.d.ts +0 -1
- package/dist/utils/worktree.d.ts.map +1 -1
- package/dist/utils/worktree.js +2 -2
- package/package.json +3 -3
- package/src/acp/agent.ts +1 -1
- package/src/components/App.tsx +6 -2
- package/src/components/BackgroundTaskManager.tsx +3 -6
- package/src/components/BangDisplay.tsx +4 -22
- package/src/components/ChatInterface.tsx +18 -33
- package/src/components/ConfirmationDetails.tsx +2 -15
- package/src/components/ConfirmationSelector.tsx +3 -15
- package/src/components/DiffDisplay.tsx +2 -27
- package/src/components/MessageList.tsx +17 -20
- package/src/components/RewindCommand.tsx +3 -5
- package/src/components/StatusCommand.tsx +7 -0
- package/src/components/ToolDisplay.tsx +3 -6
- package/src/contexts/useChat.tsx +34 -59
- package/src/index.ts +13 -0
- package/src/utils/highlightUtils.ts +0 -8
- package/src/utils/logger.ts +12 -14
- package/src/utils/throttle.ts +75 -0
- package/src/utils/toolParameterTransforms.ts +23 -1
- package/src/utils/worktree.ts +7 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React, { useState, useCallback, useLayoutEffect } from "react";
|
|
2
|
-
import { Box, useStdout } from "ink";
|
|
1
|
+
import React, { useState, useCallback, useLayoutEffect, useRef } from "react";
|
|
2
|
+
import { Box, useStdout, measureElement } from "ink";
|
|
3
3
|
import { MessageList } from "./MessageList.js";
|
|
4
4
|
import { BtwDisplay } from "./BtwDisplay.js";
|
|
5
5
|
import { InputBox } from "./InputBox.js";
|
|
@@ -14,9 +14,6 @@ import type { PermissionDecision } from "wave-agent-sdk";
|
|
|
14
14
|
|
|
15
15
|
export const ChatInterface: React.FC = () => {
|
|
16
16
|
const { stdout } = useStdout();
|
|
17
|
-
const [detailsHeight, setDetailsHeight] = useState(0);
|
|
18
|
-
const [selectorHeight, setSelectorHeight] = useState(0);
|
|
19
|
-
const [dynamicBlocksHeight, setDynamicBlocksHeight] = useState(0);
|
|
20
17
|
const [isConfirmationTooTall, setIsConfirmationTooTall] = useState(false);
|
|
21
18
|
|
|
22
19
|
const {
|
|
@@ -44,26 +41,11 @@ export const ChatInterface: React.FC = () => {
|
|
|
44
41
|
btwState,
|
|
45
42
|
} = useChat();
|
|
46
43
|
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
const handleDetailsHeightMeasured = useCallback((height: number) => {
|
|
50
|
-
setDetailsHeight(height);
|
|
51
|
-
}, []);
|
|
52
|
-
|
|
53
|
-
const handleSelectorHeightMeasured = useCallback((height: number) => {
|
|
54
|
-
setSelectorHeight(height);
|
|
55
|
-
}, []);
|
|
56
|
-
|
|
57
|
-
const handleDynamicBlocksHeightMeasured = useCallback((height: number) => {
|
|
58
|
-
setDynamicBlocksHeight(height);
|
|
59
|
-
}, []);
|
|
44
|
+
const interfaceRef = useRef(null);
|
|
60
45
|
|
|
61
46
|
useLayoutEffect(() => {
|
|
62
47
|
if (!isConfirmationVisible) {
|
|
63
48
|
setIsConfirmationTooTall(false);
|
|
64
|
-
setDetailsHeight(0);
|
|
65
|
-
setSelectorHeight(0);
|
|
66
|
-
setDynamicBlocksHeight(0);
|
|
67
49
|
return;
|
|
68
50
|
}
|
|
69
51
|
|
|
@@ -71,20 +53,26 @@ export const ChatInterface: React.FC = () => {
|
|
|
71
53
|
return;
|
|
72
54
|
}
|
|
73
55
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
56
|
+
if (interfaceRef.current) {
|
|
57
|
+
const { height } = measureElement(interfaceRef.current);
|
|
58
|
+
const terminalHeight = stdout?.rows || 24;
|
|
59
|
+
if (height > terminalHeight - 3) {
|
|
60
|
+
setIsConfirmationTooTall(true);
|
|
61
|
+
}
|
|
78
62
|
}
|
|
79
63
|
}, [
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
64
|
+
messages,
|
|
65
|
+
isLoading,
|
|
66
|
+
isCommandRunning,
|
|
67
|
+
isCompressing,
|
|
68
|
+
isExpanded,
|
|
84
69
|
isConfirmationVisible,
|
|
85
70
|
isConfirmationTooTall,
|
|
71
|
+
stdout?.rows,
|
|
86
72
|
]);
|
|
87
73
|
|
|
74
|
+
const displayMessages = messages;
|
|
75
|
+
|
|
88
76
|
const handleConfirmationCancel = useCallback(() => {
|
|
89
77
|
if (isConfirmationTooTall) {
|
|
90
78
|
setWasLastDetailsTooTall((prev) => prev + 1);
|
|
@@ -115,14 +103,13 @@ export const ChatInterface: React.FC = () => {
|
|
|
115
103
|
if (!sessionId) return null;
|
|
116
104
|
|
|
117
105
|
return (
|
|
118
|
-
<Box flexDirection="column">
|
|
106
|
+
<Box ref={interfaceRef} flexDirection="column">
|
|
119
107
|
<MessageList
|
|
120
108
|
messages={displayMessages}
|
|
121
109
|
isExpanded={isExpanded}
|
|
122
110
|
forceStatic={isConfirmationVisible && isConfirmationTooTall}
|
|
123
111
|
version={version}
|
|
124
112
|
workdir={workdir}
|
|
125
|
-
onDynamicBlocksHeightMeasured={handleDynamicBlocksHeightMeasured}
|
|
126
113
|
/>
|
|
127
114
|
|
|
128
115
|
{!isConfirmationVisible && !isExpanded && (
|
|
@@ -159,7 +146,6 @@ export const ChatInterface: React.FC = () => {
|
|
|
159
146
|
toolInput={confirmingTool!.input}
|
|
160
147
|
planContent={confirmingTool!.planContent}
|
|
161
148
|
isExpanded={isExpanded}
|
|
162
|
-
onHeightMeasured={handleDetailsHeightMeasured}
|
|
163
149
|
isStatic={isConfirmationTooTall}
|
|
164
150
|
/>
|
|
165
151
|
<ConfirmationSelector
|
|
@@ -171,7 +157,6 @@ export const ChatInterface: React.FC = () => {
|
|
|
171
157
|
onDecision={wrappedHandleConfirmationDecision}
|
|
172
158
|
onCancel={handleConfirmationCancel}
|
|
173
159
|
onAbort={abortMessage}
|
|
174
|
-
onHeightMeasured={handleSelectorHeightMeasured}
|
|
175
160
|
/>
|
|
176
161
|
</>
|
|
177
162
|
)}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import { Box, Text,
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text, Static } from "ink";
|
|
3
3
|
import {
|
|
4
4
|
BASH_TOOL_NAME,
|
|
5
5
|
EDIT_TOOL_NAME,
|
|
@@ -41,7 +41,6 @@ export interface ConfirmationDetailsProps {
|
|
|
41
41
|
toolInput?: Record<string, unknown>;
|
|
42
42
|
planContent?: string;
|
|
43
43
|
isExpanded?: boolean;
|
|
44
|
-
onHeightMeasured?: (height: number) => void;
|
|
45
44
|
isStatic?: boolean;
|
|
46
45
|
}
|
|
47
46
|
|
|
@@ -50,26 +49,14 @@ export const ConfirmationDetails: React.FC<ConfirmationDetailsProps> = ({
|
|
|
50
49
|
toolInput,
|
|
51
50
|
planContent,
|
|
52
51
|
isExpanded = false,
|
|
53
|
-
onHeightMeasured,
|
|
54
52
|
isStatic = false,
|
|
55
53
|
}) => {
|
|
56
|
-
const { stdout } = useStdout();
|
|
57
|
-
const boxRef = useRef(null);
|
|
58
|
-
|
|
59
54
|
const startLineNumber =
|
|
60
55
|
(toolInput?.startLineNumber as number | undefined) ??
|
|
61
56
|
(toolName === WRITE_TOOL_NAME ? 1 : undefined);
|
|
62
57
|
|
|
63
|
-
useLayoutEffect(() => {
|
|
64
|
-
if (boxRef.current) {
|
|
65
|
-
const { height } = measureElement(boxRef.current);
|
|
66
|
-
onHeightMeasured?.(height);
|
|
67
|
-
}
|
|
68
|
-
}, [stdout?.rows, onHeightMeasured, toolInput, isExpanded]);
|
|
69
|
-
|
|
70
58
|
const content = (
|
|
71
59
|
<Box
|
|
72
|
-
ref={boxRef}
|
|
73
60
|
flexDirection="column"
|
|
74
61
|
borderStyle="single"
|
|
75
62
|
borderColor="yellow"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React, { useEffect,
|
|
2
|
-
import { Box, Text, useInput
|
|
1
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
3
|
import type { PermissionDecision, AskUserQuestionInput } from "wave-agent-sdk";
|
|
4
4
|
import {
|
|
5
5
|
BASH_TOOL_NAME,
|
|
@@ -25,7 +25,6 @@ export interface ConfirmationSelectorProps {
|
|
|
25
25
|
onDecision: (decision: PermissionDecision) => void;
|
|
26
26
|
onCancel: () => void;
|
|
27
27
|
onAbort: () => void;
|
|
28
|
-
onHeightMeasured?: (height: number) => void;
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
interface ConfirmationState {
|
|
@@ -44,18 +43,7 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
|
|
|
44
43
|
onDecision,
|
|
45
44
|
onCancel,
|
|
46
45
|
onAbort,
|
|
47
|
-
onHeightMeasured,
|
|
48
46
|
}) => {
|
|
49
|
-
const { stdout } = useStdout();
|
|
50
|
-
const boxRef = useRef(null);
|
|
51
|
-
|
|
52
|
-
useLayoutEffect(() => {
|
|
53
|
-
if (boxRef.current) {
|
|
54
|
-
const { height } = measureElement(boxRef.current);
|
|
55
|
-
onHeightMeasured?.(height);
|
|
56
|
-
}
|
|
57
|
-
}, [stdout?.rows, onHeightMeasured, toolName, toolInput, isExpanded]);
|
|
58
|
-
|
|
59
47
|
const [state, setState] = useState<ConfirmationState>({
|
|
60
48
|
selectedOption: toolName === EXIT_PLAN_MODE_TOOL_NAME ? "clear" : "allow",
|
|
61
49
|
alternativeText: "",
|
|
@@ -497,7 +485,7 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
|
|
|
497
485
|
state.selectedOption === "alternative" && !state.hasUserInput;
|
|
498
486
|
|
|
499
487
|
return (
|
|
500
|
-
<Box
|
|
488
|
+
<Box flexDirection="column">
|
|
501
489
|
{toolName === ASK_USER_QUESTION_TOOL_NAME &&
|
|
502
490
|
currentQuestion &&
|
|
503
491
|
!isExpanded && (
|
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
import React, { useMemo } from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
-
import {
|
|
4
|
-
WRITE_TOOL_NAME,
|
|
5
|
-
EDIT_TOOL_NAME,
|
|
6
|
-
type WriteToolParameters,
|
|
7
|
-
} from "wave-agent-sdk";
|
|
3
|
+
import { WRITE_TOOL_NAME, EDIT_TOOL_NAME } from "wave-agent-sdk";
|
|
8
4
|
import { transformToolBlockToChanges } from "../utils/toolParameterTransforms.js";
|
|
9
5
|
import { diffLines, diffWords } from "diff";
|
|
10
|
-
import path from "path";
|
|
11
|
-
import { Markdown } from "./Markdown.js";
|
|
12
6
|
|
|
13
7
|
interface DiffDisplayProps {
|
|
14
8
|
toolName?: string;
|
|
@@ -103,23 +97,6 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
|
|
|
103
97
|
}
|
|
104
98
|
};
|
|
105
99
|
|
|
106
|
-
// Render highlighted code for Write tool
|
|
107
|
-
const renderWriteContent = () => {
|
|
108
|
-
if (!parameters) return null;
|
|
109
|
-
try {
|
|
110
|
-
const parsed = JSON.parse(parameters) as WriteToolParameters;
|
|
111
|
-
const content = parsed.content || "";
|
|
112
|
-
const filePath = parsed.file_path || "";
|
|
113
|
-
const ext = path.extname(filePath).slice(1);
|
|
114
|
-
|
|
115
|
-
const markdown = `\`\`\`${ext}\n${content}\n\`\`\``;
|
|
116
|
-
return <Markdown>{markdown}</Markdown>;
|
|
117
|
-
} catch (error) {
|
|
118
|
-
console.warn("Error rendering write content:", error);
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
|
|
123
100
|
// Render expanded diff display
|
|
124
101
|
const renderExpandedDiff = () => {
|
|
125
102
|
try {
|
|
@@ -375,9 +352,7 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
|
|
|
375
352
|
return (
|
|
376
353
|
<Box flexDirection="column">
|
|
377
354
|
<Box paddingLeft={2} borderLeft borderColor="cyan" flexDirection="column">
|
|
378
|
-
{
|
|
379
|
-
? renderWriteContent()
|
|
380
|
-
: renderExpandedDiff()}
|
|
355
|
+
{renderExpandedDiff()}
|
|
381
356
|
</Box>
|
|
382
357
|
</Box>
|
|
383
358
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from "react";
|
|
2
2
|
import os from "os";
|
|
3
|
-
import { Box, Text, Static
|
|
4
|
-
import type { Message } from "wave-agent-sdk";
|
|
3
|
+
import { Box, Text, Static } from "ink";
|
|
4
|
+
import type { Message, MessageBlock } from "wave-agent-sdk";
|
|
5
5
|
import { MessageBlockItem } from "./MessageBlockItem.js";
|
|
6
6
|
|
|
7
7
|
export interface MessageListProps {
|
|
@@ -10,7 +10,6 @@ export interface MessageListProps {
|
|
|
10
10
|
forceStatic?: boolean;
|
|
11
11
|
version?: string;
|
|
12
12
|
workdir?: string;
|
|
13
|
-
onDynamicBlocksHeightMeasured?: (height: number) => void;
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
export const MessageList = React.memo(
|
|
@@ -20,7 +19,6 @@ export const MessageList = React.memo(
|
|
|
20
19
|
forceStatic = false,
|
|
21
20
|
version,
|
|
22
21
|
workdir,
|
|
23
|
-
onDynamicBlocksHeightMeasured,
|
|
24
22
|
}: MessageListProps) => {
|
|
25
23
|
const welcomeMessage = (
|
|
26
24
|
<Box flexDirection="column" paddingTop={1}>
|
|
@@ -39,6 +37,18 @@ export const MessageList = React.memo(
|
|
|
39
37
|
// Filter out meta messages
|
|
40
38
|
const visibleMessages = messages.filter((m) => !m.isMeta);
|
|
41
39
|
|
|
40
|
+
const isRunning = (b: MessageBlock) =>
|
|
41
|
+
(b.type === "tool" && b.stage === "running") ||
|
|
42
|
+
(b.type === "bang" && b.isRunning) ||
|
|
43
|
+
(b.type === "slash" && b.stage === "running");
|
|
44
|
+
|
|
45
|
+
const messagesWithRunningBlocks = new Set(
|
|
46
|
+
visibleMessages
|
|
47
|
+
.map((m, i) => ({ m, i }))
|
|
48
|
+
.filter(({ m }) => m.blocks.some(isRunning))
|
|
49
|
+
.map(({ i }) => i),
|
|
50
|
+
);
|
|
51
|
+
|
|
42
52
|
// Flatten messages into blocks with metadata
|
|
43
53
|
const allBlocks = visibleMessages.flatMap((message, messageIndex) => {
|
|
44
54
|
return message.blocks.map((block, blockIndex) => ({
|
|
@@ -55,26 +65,13 @@ export const MessageList = React.memo(
|
|
|
55
65
|
const { block } = item;
|
|
56
66
|
const isDynamic =
|
|
57
67
|
!forceStatic &&
|
|
58
|
-
((
|
|
59
|
-
(block.type === "bang" && block.isRunning) ||
|
|
60
|
-
(block.type === "slash" && block.stage === "running"));
|
|
68
|
+
(messagesWithRunningBlocks.has(item.messageIndex) || isRunning(block));
|
|
61
69
|
return { ...item, isDynamic };
|
|
62
70
|
});
|
|
63
71
|
|
|
64
72
|
const staticBlocks = blocksWithStatus.filter((b) => !b.isDynamic);
|
|
65
73
|
const dynamicBlocks = blocksWithStatus.filter((b) => b.isDynamic);
|
|
66
74
|
|
|
67
|
-
const dynamicBlocksRef = useRef(null);
|
|
68
|
-
|
|
69
|
-
useLayoutEffect(() => {
|
|
70
|
-
if (dynamicBlocksRef.current) {
|
|
71
|
-
const { height } = measureElement(dynamicBlocksRef.current);
|
|
72
|
-
onDynamicBlocksHeightMeasured?.(height);
|
|
73
|
-
} else {
|
|
74
|
-
onDynamicBlocksHeightMeasured?.(0);
|
|
75
|
-
}
|
|
76
|
-
}, [dynamicBlocks, isExpanded, onDynamicBlocksHeightMeasured]);
|
|
77
|
-
|
|
78
75
|
const staticItems = [
|
|
79
76
|
{
|
|
80
77
|
isWelcome: true,
|
|
@@ -120,7 +117,7 @@ export const MessageList = React.memo(
|
|
|
120
117
|
|
|
121
118
|
{/* Dynamic blocks */}
|
|
122
119
|
{dynamicBlocks.length > 0 && (
|
|
123
|
-
<Box
|
|
120
|
+
<Box flexDirection="column">
|
|
124
121
|
{dynamicBlocks.map((item) => (
|
|
125
122
|
<MessageBlockItem
|
|
126
123
|
key={item.key}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useState } from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
|
-
import type { Message
|
|
3
|
+
import type { Message } from "wave-agent-sdk";
|
|
4
|
+
import { getMessageContent } from "wave-agent-sdk";
|
|
4
5
|
|
|
5
6
|
export interface RewindCommandProps {
|
|
6
7
|
messages: Message[];
|
|
@@ -131,10 +132,7 @@ export const RewindCommand: React.FC<RewindCommandProps> = ({
|
|
|
131
132
|
{visibleCheckpoints.map((checkpoint, index) => {
|
|
132
133
|
const actualIndex = startIndex + index;
|
|
133
134
|
const isSelected = actualIndex === selectedIndex;
|
|
134
|
-
const content = checkpoint.msg
|
|
135
|
-
.filter((b): b is TextBlock => b.type === "text")
|
|
136
|
-
.map((b) => b.content)
|
|
137
|
-
.join(" ")
|
|
135
|
+
const content = getMessageContent(checkpoint.msg)
|
|
138
136
|
.replace(/\n/g, "\\n")
|
|
139
137
|
.substring(0, 60);
|
|
140
138
|
|
|
@@ -86,6 +86,13 @@ export const StatusCommand: React.FC<StatusCommandProps> = ({ onCancel }) => {
|
|
|
86
86
|
<Text color="white">{modelConfig.model}</Text>
|
|
87
87
|
</Box>
|
|
88
88
|
|
|
89
|
+
<Box>
|
|
90
|
+
<Box width={20}>
|
|
91
|
+
<Text color="yellow">Fast model:</Text>
|
|
92
|
+
</Box>
|
|
93
|
+
<Text color="white">{modelConfig.fastModel}</Text>
|
|
94
|
+
</Box>
|
|
95
|
+
|
|
89
96
|
<Box marginTop={1}>
|
|
90
97
|
<Text dimColor>Esc to cancel</Text>
|
|
91
98
|
</Box>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import type { ToolBlock } from "wave-agent-sdk";
|
|
4
|
+
import { getLastLines } from "wave-agent-sdk";
|
|
4
5
|
import { DiffDisplay } from "./DiffDisplay.js";
|
|
5
6
|
|
|
6
7
|
interface ToolDisplayProps {
|
|
@@ -45,11 +46,7 @@ export const ToolDisplay: React.FC<ToolDisplayProps> = ({
|
|
|
45
46
|
|
|
46
47
|
// If no shortResult but has result, return last 5 lines
|
|
47
48
|
if (block.result) {
|
|
48
|
-
|
|
49
|
-
if (lines.length > 5) {
|
|
50
|
-
return lines.slice(-5).join("\n");
|
|
51
|
-
}
|
|
52
|
-
return block.result;
|
|
49
|
+
return getLastLines(block.result, 5);
|
|
53
50
|
}
|
|
54
51
|
|
|
55
52
|
return null;
|
|
@@ -81,7 +78,7 @@ export const ToolDisplay: React.FC<ToolDisplayProps> = ({
|
|
|
81
78
|
flexDirection="column"
|
|
82
79
|
>
|
|
83
80
|
{shortResult.split("\n").map((line, index) => (
|
|
84
|
-
<Text key={index} color="gray">
|
|
81
|
+
<Text key={index} color="gray" wrap="truncate-end">
|
|
85
82
|
{line}
|
|
86
83
|
</Text>
|
|
87
84
|
))}
|
package/src/contexts/useChat.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import React, {
|
|
|
5
5
|
useRef,
|
|
6
6
|
useEffect,
|
|
7
7
|
useState,
|
|
8
|
+
useMemo,
|
|
8
9
|
} from "react";
|
|
9
10
|
import { useInput } from "ink";
|
|
10
11
|
import { useAppConfig } from "./useAppConfig.js";
|
|
@@ -22,8 +23,10 @@ import {
|
|
|
22
23
|
AgentCallbacks,
|
|
23
24
|
type ToolPermissionContext,
|
|
24
25
|
OPERATION_CANCELLED_BY_USER,
|
|
26
|
+
cloneMessage,
|
|
25
27
|
} from "wave-agent-sdk";
|
|
26
28
|
import { logger } from "../utils/logger.js";
|
|
29
|
+
import { throttle } from "../utils/throttle.js";
|
|
27
30
|
import { displayUsageSummary } from "../utils/usageSummary.js";
|
|
28
31
|
import { expandLongTextPlaceholders } from "../managers/inputHandlers.js";
|
|
29
32
|
|
|
@@ -77,9 +80,6 @@ export interface ChatContextType {
|
|
|
77
80
|
// Slash Command functionality
|
|
78
81
|
slashCommands: SlashCommand[];
|
|
79
82
|
hasSlashCommand: (commandId: string) => boolean;
|
|
80
|
-
// Subagent messages
|
|
81
|
-
subagentMessages: Record<string, Message[]>;
|
|
82
|
-
subagentLatestTokens: Record<string, number>;
|
|
83
83
|
// Permission functionality
|
|
84
84
|
permissionMode: PermissionMode;
|
|
85
85
|
setPermissionMode: (mode: PermissionMode) => void;
|
|
@@ -159,10 +159,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
159
159
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
160
160
|
const isExpandedRef = useRef(isExpanded);
|
|
161
161
|
|
|
162
|
-
useEffect(() => {
|
|
163
|
-
isExpandedRef.current = isExpanded;
|
|
164
|
-
}, [isExpanded]);
|
|
165
|
-
|
|
166
162
|
const [isTaskListVisible, setIsTaskListVisible] = useState(true);
|
|
167
163
|
|
|
168
164
|
const [queuedMessages, setQueuedMessages] = useState<
|
|
@@ -173,9 +169,31 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
173
169
|
}>
|
|
174
170
|
>([]);
|
|
175
171
|
|
|
176
|
-
// AI State
|
|
177
172
|
const [messages, setMessages] = useState<Message[]>([]);
|
|
178
173
|
|
|
174
|
+
const throttledSetMessages = useMemo(
|
|
175
|
+
() =>
|
|
176
|
+
throttle(() => {
|
|
177
|
+
if (!isExpandedRef.current && agentRef.current) {
|
|
178
|
+
setMessages([...agentRef.current.messages]);
|
|
179
|
+
}
|
|
180
|
+
}, 300),
|
|
181
|
+
[],
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
isExpandedRef.current = isExpanded;
|
|
186
|
+
if (isExpanded) {
|
|
187
|
+
throttledSetMessages.cancel();
|
|
188
|
+
}
|
|
189
|
+
}, [isExpanded, throttledSetMessages]);
|
|
190
|
+
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
return () => {
|
|
193
|
+
throttledSetMessages.cancel();
|
|
194
|
+
};
|
|
195
|
+
}, [throttledSetMessages]);
|
|
196
|
+
|
|
179
197
|
const [isLoading, setIsLoading] = useState(false);
|
|
180
198
|
const [latestTotalTokens, setlatestTotalTokens] = useState(0);
|
|
181
199
|
const [sessionId, setSessionId] = useState("");
|
|
@@ -195,14 +213,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
195
213
|
// Command state
|
|
196
214
|
const [slashCommands, setSlashCommands] = useState<SlashCommand[]>([]);
|
|
197
215
|
|
|
198
|
-
// Subagent messages state
|
|
199
|
-
const [subagentMessages, setSubagentMessages] = useState<
|
|
200
|
-
Record<string, Message[]>
|
|
201
|
-
>({});
|
|
202
|
-
const [subagentLatestTokens, setSubagentLatestTokens] = useState<
|
|
203
|
-
Record<string, number>
|
|
204
|
-
>({});
|
|
205
|
-
|
|
206
216
|
// Permission state
|
|
207
217
|
const [permissionMode, setPermissionModeState] = useState<PermissionMode>(
|
|
208
218
|
initialPermissionMode ||
|
|
@@ -276,20 +286,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
276
286
|
!!bypassPermissions || initialPermissionMode === "bypassPermissions";
|
|
277
287
|
|
|
278
288
|
const agentRef = useRef<Agent | null>(null);
|
|
279
|
-
const taskUpdateTimerRef = useRef<NodeJS.Timeout | null>(null);
|
|
280
|
-
|
|
281
|
-
const debouncedSetTasks = useCallback(
|
|
282
|
-
(newTasks: Task[]) => {
|
|
283
|
-
if (taskUpdateTimerRef.current) {
|
|
284
|
-
clearTimeout(taskUpdateTimerRef.current);
|
|
285
|
-
}
|
|
286
|
-
taskUpdateTimerRef.current = setTimeout(() => {
|
|
287
|
-
setTasks([...newTasks]);
|
|
288
|
-
taskUpdateTimerRef.current = null;
|
|
289
|
-
}, 100);
|
|
290
|
-
},
|
|
291
|
-
[setTasks],
|
|
292
|
-
);
|
|
293
289
|
|
|
294
290
|
// Permission confirmation methods with queue support
|
|
295
291
|
const showConfirmation = useCallback(
|
|
@@ -323,9 +319,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
323
319
|
const initializeAgent = async () => {
|
|
324
320
|
const callbacks: AgentCallbacks = {
|
|
325
321
|
onMessagesChange: () => {
|
|
326
|
-
|
|
327
|
-
setMessages([...agentRef.current.messages]);
|
|
328
|
-
}
|
|
322
|
+
throttledSetMessages();
|
|
329
323
|
},
|
|
330
324
|
onServersChange: (servers) => {
|
|
331
325
|
setMcpServers([...servers]);
|
|
@@ -343,23 +337,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
343
337
|
setBackgroundTasks([...tasks]);
|
|
344
338
|
},
|
|
345
339
|
onTasksChange: (tasks) => {
|
|
346
|
-
|
|
347
|
-
},
|
|
348
|
-
onSubagentMessagesChange: (subagentId: string, messages: Message[]) => {
|
|
349
|
-
logger.debug("onSubagentMessagesChange", subagentId, messages.length);
|
|
350
|
-
setSubagentMessages((prev) => ({
|
|
351
|
-
...prev,
|
|
352
|
-
[subagentId]: [...messages],
|
|
353
|
-
}));
|
|
354
|
-
},
|
|
355
|
-
onSubagentLatestTotalTokensChange: (
|
|
356
|
-
subagentId: string,
|
|
357
|
-
tokens: number,
|
|
358
|
-
) => {
|
|
359
|
-
setSubagentLatestTokens((prev) => ({
|
|
360
|
-
...prev,
|
|
361
|
-
[subagentId]: tokens,
|
|
362
|
-
}));
|
|
340
|
+
setTasks([...tasks]);
|
|
363
341
|
},
|
|
364
342
|
onPermissionModeChange: (mode) => {
|
|
365
343
|
setPermissionModeState(mode);
|
|
@@ -455,17 +433,13 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
455
433
|
workdir,
|
|
456
434
|
worktreeSession,
|
|
457
435
|
model,
|
|
458
|
-
debouncedSetTasks,
|
|
459
436
|
initialPermissionMode,
|
|
437
|
+
throttledSetMessages,
|
|
460
438
|
]);
|
|
461
439
|
|
|
462
440
|
// Cleanup on unmount
|
|
463
441
|
useEffect(() => {
|
|
464
442
|
return () => {
|
|
465
|
-
if (taskUpdateTimerRef.current) {
|
|
466
|
-
clearTimeout(taskUpdateTimerRef.current);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
443
|
if (agentRef.current) {
|
|
470
444
|
try {
|
|
471
445
|
// Display usage summary before cleanup
|
|
@@ -517,7 +491,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
517
491
|
if (command) {
|
|
518
492
|
setIsCommandRunning(true);
|
|
519
493
|
try {
|
|
520
|
-
await agentRef.current?.
|
|
494
|
+
await agentRef.current?.bang(command);
|
|
521
495
|
} finally {
|
|
522
496
|
setIsCommandRunning(false);
|
|
523
497
|
}
|
|
@@ -707,13 +681,16 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
707
681
|
// Clear terminal screen when expanded state changes
|
|
708
682
|
setIsExpanded((prev) => {
|
|
709
683
|
const newExpanded = !prev;
|
|
684
|
+
isExpandedRef.current = newExpanded;
|
|
710
685
|
if (newExpanded) {
|
|
711
686
|
// Transitioning to EXPANDED: Freeze the current view
|
|
687
|
+
// Cancel any pending throttled updates to avoid overwriting the frozen state
|
|
688
|
+
throttledSetMessages.cancel();
|
|
712
689
|
// Deep copy the last message to ensure it doesn't update if the agent is still writing to it
|
|
713
690
|
setMessages((currentMessages) => {
|
|
714
691
|
if (currentMessages.length === 0) return currentMessages;
|
|
715
692
|
const lastMessage = currentMessages[currentMessages.length - 1];
|
|
716
|
-
const frozenLastMessage =
|
|
693
|
+
const frozenLastMessage = cloneMessage(lastMessage);
|
|
717
694
|
return [...currentMessages.slice(0, -1), frozenLastMessage];
|
|
718
695
|
});
|
|
719
696
|
} else {
|
|
@@ -772,8 +749,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
772
749
|
stopBackgroundTask,
|
|
773
750
|
slashCommands,
|
|
774
751
|
hasSlashCommand,
|
|
775
|
-
subagentMessages,
|
|
776
|
-
subagentLatestTokens,
|
|
777
752
|
permissionMode,
|
|
778
753
|
setPermissionMode,
|
|
779
754
|
allowBypassInCycle,
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import yargs from "yargs";
|
|
2
2
|
import { hideBin } from "yargs/helpers";
|
|
3
3
|
import { startCli } from "./cli.js";
|
|
4
|
+
import { logger } from "./utils/logger.js";
|
|
4
5
|
import { Scope, generateRandomName, type PermissionMode } from "wave-agent-sdk";
|
|
5
6
|
import { createWorktree, type WorktreeSession } from "./utils/worktree.js";
|
|
6
7
|
import path from "path";
|
|
@@ -14,6 +15,18 @@ const version = packageJson.version;
|
|
|
14
15
|
|
|
15
16
|
// Export main function for external use
|
|
16
17
|
export async function main() {
|
|
18
|
+
// Start memory monitoring in debug mode
|
|
19
|
+
if (process.env.LOG_LEVEL === "DEBUG") {
|
|
20
|
+
setInterval(() => {
|
|
21
|
+
const usage = process.memoryUsage();
|
|
22
|
+
logger.debug(
|
|
23
|
+
`[Memory] RSS: ${Math.round(usage.rss / 1024 / 1024)}MB, ` +
|
|
24
|
+
`Heap: ${Math.round(usage.heapUsed / 1024 / 1024)}/${Math.round(usage.heapTotal / 1024 / 1024)}MB, ` +
|
|
25
|
+
`External: ${Math.round(usage.external / 1024 / 1024)}MB`,
|
|
26
|
+
);
|
|
27
|
+
}, 10000).unref();
|
|
28
|
+
}
|
|
29
|
+
|
|
17
30
|
try {
|
|
18
31
|
const originalCwd = process.cwd();
|
|
19
32
|
const argv = await yargs(hideBin(process.argv))
|
|
@@ -49,14 +49,6 @@ function nodeToAnsi(node: Node): string {
|
|
|
49
49
|
|
|
50
50
|
for (const className of classes) {
|
|
51
51
|
if (theme[className]) {
|
|
52
|
-
// If content has newlines, split it and apply style to each line
|
|
53
|
-
// to ensure ANSI codes are correctly applied when splitting the final string by lines.
|
|
54
|
-
if (content.includes("\n")) {
|
|
55
|
-
return content
|
|
56
|
-
.split("\n")
|
|
57
|
-
.map((line) => theme[className](line))
|
|
58
|
-
.join("\n");
|
|
59
|
-
}
|
|
60
52
|
return theme[className](content);
|
|
61
53
|
}
|
|
62
54
|
}
|