wave-code 0.12.3 → 0.12.4
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/dist/components/App.d.ts +0 -1
- package/dist/components/App.d.ts.map +1 -1
- package/dist/components/App.js +4 -35
- package/dist/components/ChatInterface.d.ts +1 -3
- package/dist/components/ChatInterface.d.ts.map +1 -1
- package/dist/components/ChatInterface.js +29 -4
- package/dist/components/MessageList.d.ts +1 -1
- package/dist/components/MessageList.d.ts.map +1 -1
- package/dist/components/MessageList.js +3 -2
- package/dist/contexts/useChat.d.ts +3 -1
- package/dist/contexts/useChat.d.ts.map +1 -1
- package/dist/contexts/useChat.js +31 -6
- package/package.json +2 -2
- package/src/components/App.tsx +4 -45
- package/src/components/ChatInterface.tsx +54 -12
- package/src/components/MessageList.tsx +9 -2
- package/src/contexts/useChat.tsx +40 -8
package/dist/components/App.d.ts
CHANGED
|
@@ -5,7 +5,6 @@ interface AppProps extends BaseAppProps {
|
|
|
5
5
|
continueLastSession?: boolean;
|
|
6
6
|
onExit: (shouldRemove: boolean) => void;
|
|
7
7
|
}
|
|
8
|
-
export declare const ChatInterfaceWithRemount: React.FC;
|
|
9
8
|
export declare const App: React.FC<AppProps>;
|
|
10
9
|
export {};
|
|
11
10
|
//# sourceMappingURL=App.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../src/components/App.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../src/components/App.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA2C,MAAM,OAAO,CAAC;AAWhE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,UAAU,QAAS,SAAQ,YAAY;IACrC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,MAAM,EAAE,CAAC,YAAY,EAAE,OAAO,KAAK,IAAI,CAAC;CACzC;AAiGD,eAAO,MAAM,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC,QAAQ,CAmClC,CAAC"}
|
package/dist/components/App.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect,
|
|
3
|
-
import {
|
|
2
|
+
import { useState, useEffect, useCallback } from "react";
|
|
3
|
+
import { useInput } from "ink";
|
|
4
4
|
import { ChatInterface } from "./ChatInterface.js";
|
|
5
|
-
import { ChatProvider
|
|
5
|
+
import { ChatProvider } from "../contexts/useChat.js";
|
|
6
6
|
import { AppProvider } from "../contexts/useAppConfig.js";
|
|
7
7
|
import { WorktreeExitPrompt } from "./WorktreeExitPrompt.js";
|
|
8
8
|
import { hasUncommittedChanges, hasNewCommits, getDefaultRemoteBranch, } from "wave-agent-sdk";
|
|
@@ -48,38 +48,7 @@ const AppWithProviders = ({ bypassPermissions, permissionMode, pluginDirs, tools
|
|
|
48
48
|
if (isExiting && worktreeSession && worktreeStatus) {
|
|
49
49
|
return (_jsx(WorktreeExitPrompt, { name: worktreeSession.name, path: worktreeSession.path, hasUncommittedChanges: worktreeStatus.hasUncommittedChanges, hasNewCommits: worktreeStatus.hasNewCommits, onKeep: () => onExit(false), onRemove: () => onExit(true), onCancel: () => setIsExiting(false) }));
|
|
50
50
|
}
|
|
51
|
-
return (_jsx(ChatProvider, { bypassPermissions: bypassPermissions, permissionMode: permissionMode, pluginDirs: pluginDirs, tools: tools, allowedTools: allowedTools, disallowedTools: disallowedTools, workdir: workdir, worktreeSession: worktreeSession, version: version, model: model, children: _jsx(
|
|
52
|
-
};
|
|
53
|
-
export const ChatInterfaceWithRemount = () => {
|
|
54
|
-
const { stdout } = useStdout();
|
|
55
|
-
const { isExpanded, rewindId, sessionId } = useChat();
|
|
56
|
-
const [remountKey, setRemountKey] = useState(String(isExpanded) + rewindId);
|
|
57
|
-
const prevSessionId = useRef(sessionId);
|
|
58
|
-
const isRemountScheduled = useRef(false);
|
|
59
|
-
useEffect(() => {
|
|
60
|
-
const newKey = String(isExpanded) +
|
|
61
|
-
rewindId +
|
|
62
|
-
(prevSessionId.current && sessionId && prevSessionId.current !== sessionId
|
|
63
|
-
? sessionId
|
|
64
|
-
: "");
|
|
65
|
-
if (newKey !== remountKey && !isRemountScheduled.current) {
|
|
66
|
-
isRemountScheduled.current = true;
|
|
67
|
-
const timeout = setTimeout(() => {
|
|
68
|
-
stdout?.write("\u001b[2J\u001b[3J\u001b[0;0H", (err) => {
|
|
69
|
-
if (err) {
|
|
70
|
-
console.error("Failed to clear terminal:", err);
|
|
71
|
-
}
|
|
72
|
-
setRemountKey(newKey);
|
|
73
|
-
isRemountScheduled.current = false;
|
|
74
|
-
});
|
|
75
|
-
}, 100);
|
|
76
|
-
return () => clearTimeout(timeout);
|
|
77
|
-
}
|
|
78
|
-
if (sessionId) {
|
|
79
|
-
prevSessionId.current = sessionId;
|
|
80
|
-
}
|
|
81
|
-
}, [isExpanded, rewindId, sessionId, remountKey, stdout]);
|
|
82
|
-
return _jsx(ChatInterface, { remountKey: remountKey });
|
|
51
|
+
return (_jsx(ChatProvider, { bypassPermissions: bypassPermissions, permissionMode: permissionMode, pluginDirs: pluginDirs, tools: tools, allowedTools: allowedTools, disallowedTools: disallowedTools, workdir: workdir, worktreeSession: worktreeSession, version: version, model: model, children: _jsx(ChatInterface, {}) }));
|
|
83
52
|
};
|
|
84
53
|
export const App = ({ restoreSessionId, continueLastSession, bypassPermissions, permissionMode, pluginDirs, tools, allowedTools, disallowedTools, worktreeSession, workdir, version, model, onExit, }) => {
|
|
85
54
|
return (_jsx(AppProvider, { restoreSessionId: restoreSessionId, continueLastSession: continueLastSession, children: _jsx(AppWithProviders, { bypassPermissions: bypassPermissions, permissionMode: permissionMode, pluginDirs: pluginDirs, tools: tools, allowedTools: allowedTools, disallowedTools: disallowedTools, worktreeSession: worktreeSession, workdir: workdir, version: version, model: model, onExit: onExit }) }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatInterface.d.ts","sourceRoot":"","sources":["../../src/components/ChatInterface.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"ChatInterface.d.ts","sourceRoot":"","sources":["../../src/components/ChatInterface.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAc3D,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAmIjC,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useState, useRef, useEffect } from "react";
|
|
3
|
+
import { Box, useStdout, measureElement, Static } from "ink";
|
|
3
4
|
import { MessageList } from "./MessageList.js";
|
|
4
5
|
import { BtwDisplay } from "./BtwDisplay.js";
|
|
5
6
|
import { InputBox } from "./InputBox.js";
|
|
@@ -9,10 +10,34 @@ import { QueuedMessageList } from "./QueuedMessageList.js";
|
|
|
9
10
|
import { ConfirmationDetails } from "./ConfirmationDetails.js";
|
|
10
11
|
import { ConfirmationSelector } from "./ConfirmationSelector.js";
|
|
11
12
|
import { useChat } from "../contexts/useChat.js";
|
|
12
|
-
export const ChatInterface = (
|
|
13
|
-
const { messages, isLoading, isCommandRunning, isCompressing, sendMessage, abortMessage, mcpServers, connectMcpServer, disconnectMcpServer, isExpanded, sessionId, latestTotalTokens, slashCommands, hasSlashCommand, isConfirmationVisible, confirmingTool, handleConfirmationDecision, handleConfirmationCancel, version, workdir, btwState, } = useChat();
|
|
13
|
+
export const ChatInterface = () => {
|
|
14
|
+
const { messages, isLoading, isCommandRunning, isCompressing, sendMessage, abortMessage, mcpServers, connectMcpServer, disconnectMcpServer, isExpanded, sessionId, latestTotalTokens, slashCommands, hasSlashCommand, isConfirmationVisible, hasPendingConfirmations, confirmingTool, handleConfirmationDecision, handleConfirmationCancel, version, workdir, btwState, remountKey, requestRemount, } = useChat();
|
|
14
15
|
const displayMessages = messages;
|
|
16
|
+
const [forceStatic, setForceStatic] = useState(false);
|
|
17
|
+
const { stdout } = useStdout();
|
|
18
|
+
const terminalHeight = stdout?.rows ?? 24;
|
|
19
|
+
const chatInterfaceRef = useRef(null);
|
|
20
|
+
const prevForceStatic = useRef(forceStatic);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (isConfirmationVisible && chatInterfaceRef.current) {
|
|
23
|
+
const { height } = measureElement(chatInterfaceRef.current);
|
|
24
|
+
if (height > terminalHeight) {
|
|
25
|
+
setForceStatic(true);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
setForceStatic(false);
|
|
30
|
+
}
|
|
31
|
+
}, [isConfirmationVisible, terminalHeight]);
|
|
32
|
+
// Handle forceStatic transition - request remount when transitioning from true to false
|
|
33
|
+
// AND there are no more pending confirmations in the queue
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (prevForceStatic.current && !forceStatic && !hasPendingConfirmations) {
|
|
36
|
+
requestRemount();
|
|
37
|
+
}
|
|
38
|
+
prevForceStatic.current = forceStatic;
|
|
39
|
+
}, [forceStatic, hasPendingConfirmations, requestRemount]);
|
|
15
40
|
if (!sessionId)
|
|
16
41
|
return null;
|
|
17
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(MessageList, { messages: displayMessages, isExpanded: isExpanded, version: version, workdir: workdir }, remountKey), !isConfirmationVisible && !isExpanded && (_jsxs(_Fragment, { children: [_jsx(BtwDisplay, { btwState: btwState }), (isLoading || isCommandRunning || isCompressing) && (_jsx(LoadingIndicator, { isLoading: isLoading, isCommandRunning: isCommandRunning, isCompressing: isCompressing, latestTotalTokens: latestTotalTokens })), _jsx(TaskList, {}), _jsx(QueuedMessageList, {}), _jsx(InputBox, { isLoading: isLoading, isCommandRunning: isCommandRunning, sendMessage: sendMessage, abortMessage: abortMessage, mcpServers: mcpServers, connectMcpServer: connectMcpServer, disconnectMcpServer: disconnectMcpServer, slashCommands: slashCommands, hasSlashCommand: hasSlashCommand })] })), isConfirmationVisible && (_jsxs(_Fragment, { children: [_jsx(ConfirmationDetails, { toolName: confirmingTool.name, toolInput: confirmingTool.input, planContent: confirmingTool.planContent, isExpanded: isExpanded }), _jsx(ConfirmationSelector, { toolName: confirmingTool.name, toolInput: confirmingTool.input, suggestedPrefix: confirmingTool.suggestedPrefix, hidePersistentOption: confirmingTool.hidePersistentOption, isExpanded: isExpanded, onDecision: handleConfirmationDecision, onCancel: handleConfirmationCancel, onAbort: abortMessage })] }))] }));
|
|
42
|
+
return (_jsxs(Box, { ref: chatInterfaceRef, flexDirection: "column", children: [_jsx(MessageList, { messages: displayMessages, isExpanded: isExpanded, version: version, workdir: workdir, forceStatic: forceStatic }, remountKey), !isConfirmationVisible && !isExpanded && (_jsxs(_Fragment, { children: [_jsx(BtwDisplay, { btwState: btwState }), (isLoading || isCommandRunning || isCompressing) && (_jsx(LoadingIndicator, { isLoading: isLoading, isCommandRunning: isCommandRunning, isCompressing: isCompressing, latestTotalTokens: latestTotalTokens })), _jsx(TaskList, {}), _jsx(QueuedMessageList, {}), _jsx(InputBox, { isLoading: isLoading, isCommandRunning: isCommandRunning, sendMessage: sendMessage, abortMessage: abortMessage, mcpServers: mcpServers, connectMcpServer: connectMcpServer, disconnectMcpServer: disconnectMcpServer, slashCommands: slashCommands, hasSlashCommand: hasSlashCommand })] })), isConfirmationVisible && (_jsxs(_Fragment, { children: [forceStatic ? (_jsx(Static, { items: [{ key: "confirmation-details" }], children: () => (_jsx(ConfirmationDetails, { toolName: confirmingTool.name, toolInput: confirmingTool.input, planContent: confirmingTool.planContent, isExpanded: isExpanded })) })) : (_jsx(ConfirmationDetails, { toolName: confirmingTool.name, toolInput: confirmingTool.input, planContent: confirmingTool.planContent, isExpanded: isExpanded })), _jsx(ConfirmationSelector, { toolName: confirmingTool.name, toolInput: confirmingTool.input, suggestedPrefix: confirmingTool.suggestedPrefix, hidePersistentOption: confirmingTool.hidePersistentOption, isExpanded: isExpanded, onDecision: handleConfirmationDecision, onCancel: handleConfirmationCancel, onAbort: abortMessage })] }))] }));
|
|
18
43
|
};
|
|
@@ -7,5 +7,5 @@ export interface MessageListProps {
|
|
|
7
7
|
version?: string;
|
|
8
8
|
workdir?: string;
|
|
9
9
|
}
|
|
10
|
-
export declare const MessageList: React.MemoExoticComponent<({ messages, isExpanded, version, workdir }: MessageListProps) => import("react/jsx-runtime").JSX.Element>;
|
|
10
|
+
export declare const MessageList: React.MemoExoticComponent<({ messages, isExpanded, forceStatic, version, workdir, }: MessageListProps) => import("react/jsx-runtime").JSX.Element>;
|
|
11
11
|
//# sourceMappingURL=MessageList.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../src/components/MessageList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EAAE,OAAO,EAAgB,MAAM,gBAAgB,CAAC;AAG5D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../src/components/MessageList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EAAE,OAAO,EAAgB,MAAM,gBAAgB,CAAC;AAG5D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,WAAW,uFAOnB,gBAAgB,6CAiHpB,CAAC"}
|
|
@@ -3,7 +3,7 @@ import React from "react";
|
|
|
3
3
|
import os from "os";
|
|
4
4
|
import { Box, Text, Static } from "ink";
|
|
5
5
|
import { MessageBlockItem } from "./MessageBlockItem.js";
|
|
6
|
-
export const MessageList = React.memo(({ messages, isExpanded = false, version, workdir }) => {
|
|
6
|
+
export const MessageList = React.memo(({ messages, isExpanded = false, forceStatic = false, version, workdir, }) => {
|
|
7
7
|
const welcomeMessage = (_jsxs(Box, { flexDirection: "column", paddingTop: 1, children: [_jsxs(Text, { color: "gray", children: ["WAVE", version ? ` v${version}` : ""] }), workdir && (_jsx(Text, { color: "gray", wrap: "truncate-middle", children: workdir.replace(os.homedir(), "~") }))] }));
|
|
8
8
|
// Limit messages to prevent long rendering times
|
|
9
9
|
const maxMessages = 10;
|
|
@@ -29,7 +29,8 @@ export const MessageList = React.memo(({ messages, isExpanded = false, version,
|
|
|
29
29
|
// Determine which blocks are static vs dynamic
|
|
30
30
|
const blocksWithStatus = allBlocks.map((item) => {
|
|
31
31
|
const { block } = item;
|
|
32
|
-
const isDynamic =
|
|
32
|
+
const isDynamic = !forceStatic &&
|
|
33
|
+
(messagesWithRunningBlocks.has(item.messageIndex) || isRunning(block));
|
|
33
34
|
return { ...item, isDynamic };
|
|
34
35
|
});
|
|
35
36
|
const staticBlocks = blocksWithStatus.filter((b) => !b.isDynamic);
|
|
@@ -47,6 +47,7 @@ export interface ChatContextType {
|
|
|
47
47
|
setPermissionMode: (mode: PermissionMode) => void;
|
|
48
48
|
allowBypassInCycle: boolean;
|
|
49
49
|
isConfirmationVisible: boolean;
|
|
50
|
+
hasPendingConfirmations: boolean;
|
|
50
51
|
confirmingTool?: {
|
|
51
52
|
name: string;
|
|
52
53
|
input?: Record<string, unknown>;
|
|
@@ -59,7 +60,8 @@ export interface ChatContextType {
|
|
|
59
60
|
handleConfirmationDecision: (decision: PermissionDecision) => void;
|
|
60
61
|
handleConfirmationCancel: () => void;
|
|
61
62
|
backgroundCurrentTask: () => void;
|
|
62
|
-
|
|
63
|
+
remountKey: number;
|
|
64
|
+
requestRemount: () => void;
|
|
63
65
|
handleRewindSelect: (index: number) => Promise<void>;
|
|
64
66
|
getFullMessageThread: () => Promise<{
|
|
65
67
|
messages: Message[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useChat.d.ts","sourceRoot":"","sources":["../../src/contexts/useChat.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"useChat.d.ts","sourceRoot":"","sources":["../../src/contexts/useChat.tsx"],"names":[],"mappings":"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"}
|
package/dist/contexts/useChat.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { createContext, useContext, useCallback, useRef, useEffect, useState, useMemo, } from "react";
|
|
3
|
-
import { useInput } from "ink";
|
|
3
|
+
import { useInput, useStdout } from "ink";
|
|
4
4
|
import { useAppConfig } from "./useAppConfig.js";
|
|
5
5
|
import { Agent, OPERATION_CANCELLED_BY_USER, cloneMessage, } from "wave-agent-sdk";
|
|
6
6
|
import { logger } from "../utils/logger.js";
|
|
@@ -17,6 +17,7 @@ export const useChat = () => {
|
|
|
17
17
|
};
|
|
18
18
|
export const ChatProvider = ({ children, bypassPermissions, permissionMode: initialPermissionMode, pluginDirs, tools, allowedTools, disallowedTools, workdir, worktreeSession, version, model, }) => {
|
|
19
19
|
const { restoreSessionId, continueLastSession } = useAppConfig();
|
|
20
|
+
const { stdout } = useStdout();
|
|
20
21
|
// Message Display State
|
|
21
22
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
22
23
|
const isExpandedRef = useRef(isExpanded);
|
|
@@ -62,8 +63,31 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
62
63
|
const [confirmingTool, setConfirmingTool] = useState();
|
|
63
64
|
const [confirmationQueue, setConfirmationQueue] = useState([]);
|
|
64
65
|
const [currentConfirmation, setCurrentConfirmation] = useState(null);
|
|
65
|
-
//
|
|
66
|
-
const [
|
|
66
|
+
// Remount state
|
|
67
|
+
const [remountKey, setRemountKey] = useState(0);
|
|
68
|
+
const prevRemountKey = useRef(remountKey);
|
|
69
|
+
const prevSessionId = useRef(null);
|
|
70
|
+
// Track sessionId changes to trigger remount
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (prevSessionId.current &&
|
|
73
|
+
sessionId &&
|
|
74
|
+
prevSessionId.current !== sessionId) {
|
|
75
|
+
setRemountKey((prev) => prev + 1);
|
|
76
|
+
}
|
|
77
|
+
if (sessionId) {
|
|
78
|
+
prevSessionId.current = sessionId;
|
|
79
|
+
}
|
|
80
|
+
}, [sessionId]);
|
|
81
|
+
// Clear terminal when remountKey changes
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (remountKey !== prevRemountKey.current && prevRemountKey.current !== 0) {
|
|
84
|
+
stdout?.write("\u001b[2J\u001b[3J\u001b[0;0H");
|
|
85
|
+
}
|
|
86
|
+
prevRemountKey.current = remountKey;
|
|
87
|
+
}, [remountKey, stdout]);
|
|
88
|
+
const requestRemount = useCallback(() => {
|
|
89
|
+
setRemountKey((prev) => prev + 1);
|
|
90
|
+
}, []);
|
|
67
91
|
// Status metadata state
|
|
68
92
|
const [workingDirectory, setWorkingDirectory] = useState("");
|
|
69
93
|
// /btw state
|
|
@@ -370,8 +394,7 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
370
394
|
if (agentRef.current) {
|
|
371
395
|
try {
|
|
372
396
|
await agentRef.current.truncateHistory(index);
|
|
373
|
-
|
|
374
|
-
setRewindId((prev) => prev + 1);
|
|
397
|
+
setRemountKey((prev) => prev + 1);
|
|
375
398
|
}
|
|
376
399
|
catch (error) {
|
|
377
400
|
logger.error("Failed to rewind:", error);
|
|
@@ -486,13 +509,15 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
486
509
|
setPermissionMode,
|
|
487
510
|
allowBypassInCycle,
|
|
488
511
|
isConfirmationVisible,
|
|
512
|
+
hasPendingConfirmations: confirmationQueue.length > 0,
|
|
489
513
|
confirmingTool,
|
|
490
514
|
showConfirmation,
|
|
491
515
|
hideConfirmation,
|
|
492
516
|
handleConfirmationDecision,
|
|
493
517
|
handleConfirmationCancel,
|
|
494
518
|
backgroundCurrentTask,
|
|
495
|
-
|
|
519
|
+
remountKey,
|
|
520
|
+
requestRemount,
|
|
496
521
|
handleRewindSelect,
|
|
497
522
|
getFullMessageThread,
|
|
498
523
|
getGatewayConfig,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wave-code",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.4",
|
|
4
4
|
"description": "CLI-based code assistant powered by AI, built with React and Ink",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"semver": "^7.7.4",
|
|
43
43
|
"yargs": "^17.7.2",
|
|
44
44
|
"zod": "^3.23.8",
|
|
45
|
-
"wave-agent-sdk": "0.12.
|
|
45
|
+
"wave-agent-sdk": "0.12.4"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/react": "^19.1.8",
|
package/src/components/App.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import React, { useState, useEffect,
|
|
2
|
-
import {
|
|
1
|
+
import React, { useState, useEffect, useCallback } from "react";
|
|
2
|
+
import { useInput } from "ink";
|
|
3
3
|
import { ChatInterface } from "./ChatInterface.js";
|
|
4
|
-
import { ChatProvider
|
|
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 {
|
|
@@ -107,52 +107,11 @@ const AppWithProviders: React.FC<AppWithProvidersProps> = ({
|
|
|
107
107
|
version={version}
|
|
108
108
|
model={model}
|
|
109
109
|
>
|
|
110
|
-
<
|
|
110
|
+
<ChatInterface />
|
|
111
111
|
</ChatProvider>
|
|
112
112
|
);
|
|
113
113
|
};
|
|
114
114
|
|
|
115
|
-
export const ChatInterfaceWithRemount: React.FC = () => {
|
|
116
|
-
const { stdout } = useStdout();
|
|
117
|
-
const { isExpanded, rewindId, sessionId } = useChat();
|
|
118
|
-
|
|
119
|
-
const [remountKey, setRemountKey] = useState(String(isExpanded) + rewindId);
|
|
120
|
-
|
|
121
|
-
const prevSessionId = useRef(sessionId);
|
|
122
|
-
const isRemountScheduled = useRef(false);
|
|
123
|
-
|
|
124
|
-
useEffect(() => {
|
|
125
|
-
const newKey =
|
|
126
|
-
String(isExpanded) +
|
|
127
|
-
rewindId +
|
|
128
|
-
(prevSessionId.current && sessionId && prevSessionId.current !== sessionId
|
|
129
|
-
? sessionId
|
|
130
|
-
: "");
|
|
131
|
-
|
|
132
|
-
if (newKey !== remountKey && !isRemountScheduled.current) {
|
|
133
|
-
isRemountScheduled.current = true;
|
|
134
|
-
|
|
135
|
-
const timeout = setTimeout(() => {
|
|
136
|
-
stdout?.write("\u001b[2J\u001b[3J\u001b[0;0H", (err?: Error | null) => {
|
|
137
|
-
if (err) {
|
|
138
|
-
console.error("Failed to clear terminal:", err);
|
|
139
|
-
}
|
|
140
|
-
setRemountKey(newKey);
|
|
141
|
-
isRemountScheduled.current = false;
|
|
142
|
-
});
|
|
143
|
-
}, 100);
|
|
144
|
-
|
|
145
|
-
return () => clearTimeout(timeout);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (sessionId) {
|
|
149
|
-
prevSessionId.current = sessionId;
|
|
150
|
-
}
|
|
151
|
-
}, [isExpanded, rewindId, sessionId, remountKey, stdout]);
|
|
152
|
-
|
|
153
|
-
return <ChatInterface remountKey={remountKey} />;
|
|
154
|
-
};
|
|
155
|
-
|
|
156
115
|
export const App: React.FC<AppProps> = ({
|
|
157
116
|
restoreSessionId,
|
|
158
117
|
continueLastSession,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Box } from "ink";
|
|
1
|
+
import React, { useState, useRef, useEffect } from "react";
|
|
2
|
+
import { Box, useStdout, measureElement, Static } from "ink";
|
|
3
|
+
import type { DOMElement } from "ink";
|
|
3
4
|
import { MessageList } from "./MessageList.js";
|
|
4
5
|
import { BtwDisplay } from "./BtwDisplay.js";
|
|
5
6
|
import { InputBox } from "./InputBox.js";
|
|
@@ -11,9 +12,7 @@ import { ConfirmationSelector } from "./ConfirmationSelector.js";
|
|
|
11
12
|
|
|
12
13
|
import { useChat } from "../contexts/useChat.js";
|
|
13
14
|
|
|
14
|
-
export const ChatInterface: React.FC
|
|
15
|
-
remountKey,
|
|
16
|
-
}) => {
|
|
15
|
+
export const ChatInterface: React.FC = () => {
|
|
17
16
|
const {
|
|
18
17
|
messages,
|
|
19
18
|
isLoading,
|
|
@@ -30,26 +29,56 @@ export const ChatInterface: React.FC<{ remountKey: string }> = ({
|
|
|
30
29
|
slashCommands,
|
|
31
30
|
hasSlashCommand,
|
|
32
31
|
isConfirmationVisible,
|
|
32
|
+
hasPendingConfirmations,
|
|
33
33
|
confirmingTool,
|
|
34
34
|
handleConfirmationDecision,
|
|
35
35
|
handleConfirmationCancel,
|
|
36
36
|
version,
|
|
37
37
|
workdir,
|
|
38
38
|
btwState,
|
|
39
|
+
remountKey,
|
|
40
|
+
requestRemount,
|
|
39
41
|
} = useChat();
|
|
40
42
|
|
|
41
43
|
const displayMessages = messages;
|
|
42
44
|
|
|
45
|
+
const [forceStatic, setForceStatic] = useState(false);
|
|
46
|
+
const { stdout } = useStdout();
|
|
47
|
+
const terminalHeight = stdout?.rows ?? 24;
|
|
48
|
+
const chatInterfaceRef = useRef<DOMElement>(null);
|
|
49
|
+
const prevForceStatic = useRef(forceStatic);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (isConfirmationVisible && chatInterfaceRef.current) {
|
|
53
|
+
const { height } = measureElement(chatInterfaceRef.current);
|
|
54
|
+
if (height > terminalHeight) {
|
|
55
|
+
setForceStatic(true);
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
setForceStatic(false);
|
|
59
|
+
}
|
|
60
|
+
}, [isConfirmationVisible, terminalHeight]);
|
|
61
|
+
|
|
62
|
+
// Handle forceStatic transition - request remount when transitioning from true to false
|
|
63
|
+
// AND there are no more pending confirmations in the queue
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (prevForceStatic.current && !forceStatic && !hasPendingConfirmations) {
|
|
66
|
+
requestRemount();
|
|
67
|
+
}
|
|
68
|
+
prevForceStatic.current = forceStatic;
|
|
69
|
+
}, [forceStatic, hasPendingConfirmations, requestRemount]);
|
|
70
|
+
|
|
43
71
|
if (!sessionId) return null;
|
|
44
72
|
|
|
45
73
|
return (
|
|
46
|
-
<Box flexDirection="column">
|
|
74
|
+
<Box ref={chatInterfaceRef} flexDirection="column">
|
|
47
75
|
<MessageList
|
|
48
76
|
key={remountKey}
|
|
49
77
|
messages={displayMessages}
|
|
50
78
|
isExpanded={isExpanded}
|
|
51
79
|
version={version}
|
|
52
80
|
workdir={workdir}
|
|
81
|
+
forceStatic={forceStatic}
|
|
53
82
|
/>
|
|
54
83
|
|
|
55
84
|
{!isConfirmationVisible && !isExpanded && (
|
|
@@ -81,12 +110,25 @@ export const ChatInterface: React.FC<{ remountKey: string }> = ({
|
|
|
81
110
|
|
|
82
111
|
{isConfirmationVisible && (
|
|
83
112
|
<>
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
113
|
+
{forceStatic ? (
|
|
114
|
+
<Static items={[{ key: "confirmation-details" }]}>
|
|
115
|
+
{() => (
|
|
116
|
+
<ConfirmationDetails
|
|
117
|
+
toolName={confirmingTool!.name}
|
|
118
|
+
toolInput={confirmingTool!.input}
|
|
119
|
+
planContent={confirmingTool!.planContent}
|
|
120
|
+
isExpanded={isExpanded}
|
|
121
|
+
/>
|
|
122
|
+
)}
|
|
123
|
+
</Static>
|
|
124
|
+
) : (
|
|
125
|
+
<ConfirmationDetails
|
|
126
|
+
toolName={confirmingTool!.name}
|
|
127
|
+
toolInput={confirmingTool!.input}
|
|
128
|
+
planContent={confirmingTool!.planContent}
|
|
129
|
+
isExpanded={isExpanded}
|
|
130
|
+
/>
|
|
131
|
+
)}
|
|
90
132
|
<ConfirmationSelector
|
|
91
133
|
toolName={confirmingTool!.name}
|
|
92
134
|
toolInput={confirmingTool!.input}
|
|
@@ -13,7 +13,13 @@ export interface MessageListProps {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export const MessageList = React.memo(
|
|
16
|
-
({
|
|
16
|
+
({
|
|
17
|
+
messages,
|
|
18
|
+
isExpanded = false,
|
|
19
|
+
forceStatic = false,
|
|
20
|
+
version,
|
|
21
|
+
workdir,
|
|
22
|
+
}: MessageListProps) => {
|
|
17
23
|
const welcomeMessage = (
|
|
18
24
|
<Box flexDirection="column" paddingTop={1}>
|
|
19
25
|
<Text color="gray">WAVE{version ? ` v${version}` : ""}</Text>
|
|
@@ -58,7 +64,8 @@ export const MessageList = React.memo(
|
|
|
58
64
|
const blocksWithStatus = allBlocks.map((item) => {
|
|
59
65
|
const { block } = item;
|
|
60
66
|
const isDynamic =
|
|
61
|
-
|
|
67
|
+
!forceStatic &&
|
|
68
|
+
(messagesWithRunningBlocks.has(item.messageIndex) || isRunning(block));
|
|
62
69
|
return { ...item, isDynamic };
|
|
63
70
|
});
|
|
64
71
|
|
package/src/contexts/useChat.tsx
CHANGED
|
@@ -7,7 +7,7 @@ import React, {
|
|
|
7
7
|
useState,
|
|
8
8
|
useMemo,
|
|
9
9
|
} from "react";
|
|
10
|
-
import { useInput } from "ink";
|
|
10
|
+
import { useInput, useStdout } from "ink";
|
|
11
11
|
import { useAppConfig } from "./useAppConfig.js";
|
|
12
12
|
import type {
|
|
13
13
|
Message,
|
|
@@ -86,6 +86,7 @@ export interface ChatContextType {
|
|
|
86
86
|
allowBypassInCycle: boolean;
|
|
87
87
|
// Permission confirmation state
|
|
88
88
|
isConfirmationVisible: boolean;
|
|
89
|
+
hasPendingConfirmations: boolean;
|
|
89
90
|
confirmingTool?: {
|
|
90
91
|
name: string;
|
|
91
92
|
input?: Record<string, unknown>;
|
|
@@ -105,8 +106,10 @@ export interface ChatContextType {
|
|
|
105
106
|
handleConfirmationCancel: () => void;
|
|
106
107
|
// Background current task
|
|
107
108
|
backgroundCurrentTask: () => void;
|
|
109
|
+
// Remount functionality
|
|
110
|
+
remountKey: number;
|
|
111
|
+
requestRemount: () => void;
|
|
108
112
|
// Rewind functionality
|
|
109
|
-
rewindId: number;
|
|
110
113
|
handleRewindSelect: (index: number) => Promise<void>;
|
|
111
114
|
getFullMessageThread: () => Promise<{
|
|
112
115
|
messages: Message[];
|
|
@@ -152,6 +155,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
152
155
|
model,
|
|
153
156
|
}) => {
|
|
154
157
|
const { restoreSessionId, continueLastSession } = useAppConfig();
|
|
158
|
+
const { stdout } = useStdout();
|
|
155
159
|
|
|
156
160
|
// Message Display State
|
|
157
161
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
@@ -250,8 +254,36 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
250
254
|
reject: () => void;
|
|
251
255
|
} | null>(null);
|
|
252
256
|
|
|
253
|
-
//
|
|
254
|
-
const [
|
|
257
|
+
// Remount state
|
|
258
|
+
const [remountKey, setRemountKey] = useState(0);
|
|
259
|
+
const prevRemountKey = useRef(remountKey);
|
|
260
|
+
const prevSessionId = useRef<string | null>(null);
|
|
261
|
+
|
|
262
|
+
// Track sessionId changes to trigger remount
|
|
263
|
+
useEffect(() => {
|
|
264
|
+
if (
|
|
265
|
+
prevSessionId.current &&
|
|
266
|
+
sessionId &&
|
|
267
|
+
prevSessionId.current !== sessionId
|
|
268
|
+
) {
|
|
269
|
+
setRemountKey((prev) => prev + 1);
|
|
270
|
+
}
|
|
271
|
+
if (sessionId) {
|
|
272
|
+
prevSessionId.current = sessionId;
|
|
273
|
+
}
|
|
274
|
+
}, [sessionId]);
|
|
275
|
+
|
|
276
|
+
// Clear terminal when remountKey changes
|
|
277
|
+
useEffect(() => {
|
|
278
|
+
if (remountKey !== prevRemountKey.current && prevRemountKey.current !== 0) {
|
|
279
|
+
stdout?.write("\u001b[2J\u001b[3J\u001b[0;0H");
|
|
280
|
+
}
|
|
281
|
+
prevRemountKey.current = remountKey;
|
|
282
|
+
}, [remountKey, stdout]);
|
|
283
|
+
|
|
284
|
+
const requestRemount = useCallback(() => {
|
|
285
|
+
setRemountKey((prev) => prev + 1);
|
|
286
|
+
}, []);
|
|
255
287
|
|
|
256
288
|
// Status metadata state
|
|
257
289
|
const [workingDirectory, setWorkingDirectory] = useState("");
|
|
@@ -627,9 +659,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
627
659
|
if (agentRef.current) {
|
|
628
660
|
try {
|
|
629
661
|
await agentRef.current.truncateHistory(index);
|
|
630
|
-
|
|
631
|
-
// Clear terminal screen after rewind
|
|
632
|
-
setRewindId((prev) => prev + 1);
|
|
662
|
+
setRemountKey((prev) => prev + 1);
|
|
633
663
|
} catch (error) {
|
|
634
664
|
logger.error("Failed to rewind:", error);
|
|
635
665
|
}
|
|
@@ -749,13 +779,15 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
749
779
|
setPermissionMode,
|
|
750
780
|
allowBypassInCycle,
|
|
751
781
|
isConfirmationVisible,
|
|
782
|
+
hasPendingConfirmations: confirmationQueue.length > 0,
|
|
752
783
|
confirmingTool,
|
|
753
784
|
showConfirmation,
|
|
754
785
|
hideConfirmation,
|
|
755
786
|
handleConfirmationDecision,
|
|
756
787
|
handleConfirmationCancel,
|
|
757
788
|
backgroundCurrentTask,
|
|
758
|
-
|
|
789
|
+
remountKey,
|
|
790
|
+
requestRemount,
|
|
759
791
|
handleRewindSelect,
|
|
760
792
|
getFullMessageThread,
|
|
761
793
|
|