wave-code 0.12.2 → 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 -43
- package/dist/components/ChatInterface.d.ts.map +1 -1
- package/dist/components/ChatInterface.js +24 -50
- package/dist/components/ConfirmationDetails.d.ts +0 -1
- package/dist/components/ConfirmationDetails.d.ts.map +1 -1
- package/dist/components/ConfirmationDetails.js +2 -6
- package/dist/contexts/useChat.d.ts +3 -3
- package/dist/contexts/useChat.d.ts.map +1 -1
- package/dist/contexts/useChat.js +31 -10
- package/package.json +2 -2
- package/src/components/App.tsx +4 -55
- package/src/components/ChatInterface.tsx +51 -70
- package/src/components/ConfirmationDetails.tsx +1 -11
- package/src/contexts/useChat.tsx +41 -14
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,46 +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, wasLastDetailsTooTall, sessionId } = useChat();
|
|
56
|
-
const [remountKey, setRemountKey] = useState(String(isExpanded) + rewindId + wasLastDetailsTooTall);
|
|
57
|
-
const prevSessionId = useRef(sessionId);
|
|
58
|
-
const isRemountScheduled = useRef(false);
|
|
59
|
-
useEffect(() => {
|
|
60
|
-
const newKey = String(isExpanded) +
|
|
61
|
-
rewindId +
|
|
62
|
-
wasLastDetailsTooTall +
|
|
63
|
-
(prevSessionId.current && sessionId && prevSessionId.current !== sessionId
|
|
64
|
-
? sessionId
|
|
65
|
-
: "");
|
|
66
|
-
if (newKey !== remountKey && !isRemountScheduled.current) {
|
|
67
|
-
isRemountScheduled.current = true;
|
|
68
|
-
const timeout = setTimeout(() => {
|
|
69
|
-
stdout?.write("\u001b[2J\u001b[3J\u001b[0;0H", (err) => {
|
|
70
|
-
if (err) {
|
|
71
|
-
console.error("Failed to clear terminal:", err);
|
|
72
|
-
}
|
|
73
|
-
setRemountKey(newKey);
|
|
74
|
-
isRemountScheduled.current = false;
|
|
75
|
-
});
|
|
76
|
-
}, 100);
|
|
77
|
-
return () => clearTimeout(timeout);
|
|
78
|
-
}
|
|
79
|
-
if (sessionId) {
|
|
80
|
-
prevSessionId.current = sessionId;
|
|
81
|
-
}
|
|
82
|
-
}, [
|
|
83
|
-
isExpanded,
|
|
84
|
-
rewindId,
|
|
85
|
-
wasLastDetailsTooTall,
|
|
86
|
-
sessionId,
|
|
87
|
-
remountKey,
|
|
88
|
-
stdout,
|
|
89
|
-
]);
|
|
90
|
-
return _jsx(ChatInterface, {}, 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, {}) }));
|
|
91
52
|
};
|
|
92
53
|
export const App = ({ restoreSessionId, continueLastSession, bypassPermissions, permissionMode, pluginDirs, tools, allowedTools, disallowedTools, worktreeSession, workdir, version, model, onExit, }) => {
|
|
93
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,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState,
|
|
3
|
-
import { Box, useStdout, measureElement } from "ink";
|
|
2
|
+
import { useState, useRef, useEffect } from "react";
|
|
3
|
+
import { Box, useStdout, measureElement, Static } from "ink";
|
|
4
4
|
import { MessageList } from "./MessageList.js";
|
|
5
5
|
import { BtwDisplay } from "./BtwDisplay.js";
|
|
6
6
|
import { InputBox } from "./InputBox.js";
|
|
@@ -11,59 +11,33 @@ import { ConfirmationDetails } from "./ConfirmationDetails.js";
|
|
|
11
11
|
import { ConfirmationSelector } from "./ConfirmationSelector.js";
|
|
12
12
|
import { useChat } from "../contexts/useChat.js";
|
|
13
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();
|
|
15
|
+
const displayMessages = messages;
|
|
16
|
+
const [forceStatic, setForceStatic] = useState(false);
|
|
14
17
|
const { stdout } = useStdout();
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
if (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (isConfirmationTooTall) {
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
if (interfaceRef.current) {
|
|
27
|
-
const { height } = measureElement(interfaceRef.current);
|
|
28
|
-
const terminalHeight = stdout?.rows || 24;
|
|
29
|
-
if (height > terminalHeight - 3) {
|
|
30
|
-
setIsConfirmationTooTall(true);
|
|
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);
|
|
31
26
|
}
|
|
32
27
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
isLoading,
|
|
36
|
-
isCommandRunning,
|
|
37
|
-
isCompressing,
|
|
38
|
-
isExpanded,
|
|
39
|
-
isConfirmationVisible,
|
|
40
|
-
isConfirmationTooTall,
|
|
41
|
-
stdout?.rows,
|
|
42
|
-
]);
|
|
43
|
-
const displayMessages = messages;
|
|
44
|
-
const handleConfirmationCancel = useCallback(() => {
|
|
45
|
-
if (isConfirmationTooTall) {
|
|
46
|
-
setWasLastDetailsTooTall((prev) => prev + 1);
|
|
47
|
-
setIsConfirmationTooTall(false);
|
|
28
|
+
else {
|
|
29
|
+
setForceStatic(false);
|
|
48
30
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const wrappedHandleConfirmationDecision = useCallback((decision) => {
|
|
56
|
-
if (isConfirmationTooTall) {
|
|
57
|
-
setWasLastDetailsTooTall((prev) => prev + 1);
|
|
58
|
-
setIsConfirmationTooTall(false);
|
|
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();
|
|
59
37
|
}
|
|
60
|
-
|
|
61
|
-
}, [
|
|
62
|
-
isConfirmationTooTall,
|
|
63
|
-
handleConfirmationDecision,
|
|
64
|
-
setWasLastDetailsTooTall,
|
|
65
|
-
]);
|
|
38
|
+
prevForceStatic.current = forceStatic;
|
|
39
|
+
}, [forceStatic, hasPendingConfirmations, requestRemount]);
|
|
66
40
|
if (!sessionId)
|
|
67
41
|
return null;
|
|
68
|
-
return (_jsxs(Box, { ref:
|
|
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 })] }))] }));
|
|
69
43
|
};
|
|
@@ -4,7 +4,6 @@ export interface ConfirmationDetailsProps {
|
|
|
4
4
|
toolInput?: Record<string, unknown>;
|
|
5
5
|
planContent?: string;
|
|
6
6
|
isExpanded?: boolean;
|
|
7
|
-
isStatic?: boolean;
|
|
8
7
|
}
|
|
9
8
|
export declare const ConfirmationDetails: React.FC<ConfirmationDetailsProps>;
|
|
10
9
|
//# sourceMappingURL=ConfirmationDetails.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConfirmationDetails.d.ts","sourceRoot":"","sources":["../../src/components/ConfirmationDetails.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAsC1B,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ConfirmationDetails.d.ts","sourceRoot":"","sources":["../../src/components/ConfirmationDetails.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAsC1B,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CAoDlE,CAAC"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import
|
|
3
|
-
import { Box, Text, Static } from "ink";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
4
3
|
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
4
|
import { DiffDisplay } from "./DiffDisplay.js";
|
|
6
5
|
import { PlanDisplay } from "./PlanDisplay.js";
|
|
@@ -25,7 +24,7 @@ const getActionDescription = (toolName, toolInput) => {
|
|
|
25
24
|
return "Execute operation";
|
|
26
25
|
}
|
|
27
26
|
};
|
|
28
|
-
export const ConfirmationDetails = ({ toolName, toolInput, planContent, isExpanded = false,
|
|
27
|
+
export const ConfirmationDetails = ({ toolName, toolInput, planContent, isExpanded = false, }) => {
|
|
29
28
|
const startLineNumber = toolInput?.startLineNumber ??
|
|
30
29
|
(toolName === WRITE_TOOL_NAME ? 1 : undefined);
|
|
31
30
|
const content = (_jsxs(Box, { 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 !== WRITE_TOOL_NAME &&
|
|
@@ -36,9 +35,6 @@ export const ConfirmationDetails = ({ toolName, toolInput, planContent, isExpand
|
|
|
36
35
|
!!toolInput && (_jsx(Box, { paddingLeft: 2, borderLeft: true, borderColor: "cyan", children: _jsx(Text, { children: highlightToAnsi(JSON.stringify(toolInput, null, 2), "json") }) })), toolName !== ASK_USER_QUESTION_TOOL_NAME &&
|
|
37
36
|
toolName === EXIT_PLAN_MODE_TOOL_NAME &&
|
|
38
37
|
!!planContent && (_jsx(PlanDisplay, { plan: planContent, isExpanded: isExpanded }))] }));
|
|
39
|
-
if (isStatic) {
|
|
40
|
-
return (_jsx(Static, { items: [1], children: (item) => _jsx(React.Fragment, { children: content }, item) }));
|
|
41
|
-
}
|
|
42
38
|
return content;
|
|
43
39
|
};
|
|
44
40
|
ConfirmationDetails.displayName = "ConfirmationDetails";
|
|
@@ -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,14 +60,13 @@ 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[];
|
|
66
68
|
sessionIds: string[];
|
|
67
69
|
}>;
|
|
68
|
-
wasLastDetailsTooTall: number;
|
|
69
|
-
setWasLastDetailsTooTall: React.Dispatch<React.SetStateAction<number>>;
|
|
70
70
|
getGatewayConfig: () => import("wave-agent-sdk").GatewayConfig;
|
|
71
71
|
getModelConfig: () => import("wave-agent-sdk").ModelConfig;
|
|
72
72
|
workingDirectory: string;
|
|
@@ -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
|
|
@@ -81,8 +105,6 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
81
105
|
return newState;
|
|
82
106
|
});
|
|
83
107
|
}, []);
|
|
84
|
-
// Confirmation too tall state
|
|
85
|
-
const [wasLastDetailsTooTall, setWasLastDetailsTooTall] = useState(0);
|
|
86
108
|
const allowBypassInCycle = !!bypassPermissions || initialPermissionMode === "bypassPermissions";
|
|
87
109
|
const agentRef = useRef(null);
|
|
88
110
|
// Permission confirmation methods with queue support
|
|
@@ -372,8 +394,7 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
372
394
|
if (agentRef.current) {
|
|
373
395
|
try {
|
|
374
396
|
await agentRef.current.truncateHistory(index);
|
|
375
|
-
|
|
376
|
-
setRewindId((prev) => prev + 1);
|
|
397
|
+
setRemountKey((prev) => prev + 1);
|
|
377
398
|
}
|
|
378
399
|
catch (error) {
|
|
379
400
|
logger.error("Failed to rewind:", error);
|
|
@@ -488,17 +509,17 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
488
509
|
setPermissionMode,
|
|
489
510
|
allowBypassInCycle,
|
|
490
511
|
isConfirmationVisible,
|
|
512
|
+
hasPendingConfirmations: confirmationQueue.length > 0,
|
|
491
513
|
confirmingTool,
|
|
492
514
|
showConfirmation,
|
|
493
515
|
hideConfirmation,
|
|
494
516
|
handleConfirmationDecision,
|
|
495
517
|
handleConfirmationCancel,
|
|
496
518
|
backgroundCurrentTask,
|
|
497
|
-
|
|
519
|
+
remountKey,
|
|
520
|
+
requestRemount,
|
|
498
521
|
handleRewindSelect,
|
|
499
522
|
getFullMessageThread,
|
|
500
|
-
wasLastDetailsTooTall,
|
|
501
|
-
setWasLastDetailsTooTall,
|
|
502
523
|
getGatewayConfig,
|
|
503
524
|
getModelConfig,
|
|
504
525
|
workingDirectory,
|
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,62 +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, wasLastDetailsTooTall, sessionId } = useChat();
|
|
118
|
-
|
|
119
|
-
const [remountKey, setRemountKey] = useState(
|
|
120
|
-
String(isExpanded) + rewindId + wasLastDetailsTooTall,
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
const prevSessionId = useRef(sessionId);
|
|
124
|
-
const isRemountScheduled = useRef(false);
|
|
125
|
-
|
|
126
|
-
useEffect(() => {
|
|
127
|
-
const newKey =
|
|
128
|
-
String(isExpanded) +
|
|
129
|
-
rewindId +
|
|
130
|
-
wasLastDetailsTooTall +
|
|
131
|
-
(prevSessionId.current && sessionId && prevSessionId.current !== sessionId
|
|
132
|
-
? sessionId
|
|
133
|
-
: "");
|
|
134
|
-
|
|
135
|
-
if (newKey !== remountKey && !isRemountScheduled.current) {
|
|
136
|
-
isRemountScheduled.current = true;
|
|
137
|
-
|
|
138
|
-
const timeout = setTimeout(() => {
|
|
139
|
-
stdout?.write("\u001b[2J\u001b[3J\u001b[0;0H", (err?: Error | null) => {
|
|
140
|
-
if (err) {
|
|
141
|
-
console.error("Failed to clear terminal:", err);
|
|
142
|
-
}
|
|
143
|
-
setRemountKey(newKey);
|
|
144
|
-
isRemountScheduled.current = false;
|
|
145
|
-
});
|
|
146
|
-
}, 100);
|
|
147
|
-
|
|
148
|
-
return () => clearTimeout(timeout);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (sessionId) {
|
|
152
|
-
prevSessionId.current = sessionId;
|
|
153
|
-
}
|
|
154
|
-
}, [
|
|
155
|
-
isExpanded,
|
|
156
|
-
rewindId,
|
|
157
|
-
wasLastDetailsTooTall,
|
|
158
|
-
sessionId,
|
|
159
|
-
remountKey,
|
|
160
|
-
stdout,
|
|
161
|
-
]);
|
|
162
|
-
|
|
163
|
-
return <ChatInterface key={remountKey} />;
|
|
164
|
-
};
|
|
165
|
-
|
|
166
115
|
export const App: React.FC<AppProps> = ({
|
|
167
116
|
restoreSessionId,
|
|
168
117
|
continueLastSession,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import React, { useState,
|
|
2
|
-
import { Box, useStdout, measureElement } 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";
|
|
@@ -10,12 +11,8 @@ import { ConfirmationDetails } from "./ConfirmationDetails.js";
|
|
|
10
11
|
import { ConfirmationSelector } from "./ConfirmationSelector.js";
|
|
11
12
|
|
|
12
13
|
import { useChat } from "../contexts/useChat.js";
|
|
13
|
-
import type { PermissionDecision } from "wave-agent-sdk";
|
|
14
14
|
|
|
15
15
|
export const ChatInterface: React.FC = () => {
|
|
16
|
-
const { stdout } = useStdout();
|
|
17
|
-
const [isConfirmationTooTall, setIsConfirmationTooTall] = useState(false);
|
|
18
|
-
|
|
19
16
|
const {
|
|
20
17
|
messages,
|
|
21
18
|
isLoading,
|
|
@@ -32,84 +29,56 @@ export const ChatInterface: React.FC = () => {
|
|
|
32
29
|
slashCommands,
|
|
33
30
|
hasSlashCommand,
|
|
34
31
|
isConfirmationVisible,
|
|
32
|
+
hasPendingConfirmations,
|
|
35
33
|
confirmingTool,
|
|
36
34
|
handleConfirmationDecision,
|
|
37
|
-
handleConfirmationCancel
|
|
38
|
-
setWasLastDetailsTooTall,
|
|
35
|
+
handleConfirmationCancel,
|
|
39
36
|
version,
|
|
40
37
|
workdir,
|
|
41
38
|
btwState,
|
|
39
|
+
remountKey,
|
|
40
|
+
requestRemount,
|
|
42
41
|
} = useChat();
|
|
43
42
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
useLayoutEffect(() => {
|
|
47
|
-
if (!isConfirmationVisible) {
|
|
48
|
-
setIsConfirmationTooTall(false);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
43
|
+
const displayMessages = messages;
|
|
51
44
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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);
|
|
55
50
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
if (height > terminalHeight
|
|
60
|
-
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (isConfirmationVisible && chatInterfaceRef.current) {
|
|
53
|
+
const { height } = measureElement(chatInterfaceRef.current);
|
|
54
|
+
if (height > terminalHeight) {
|
|
55
|
+
setForceStatic(true);
|
|
61
56
|
}
|
|
57
|
+
} else {
|
|
58
|
+
setForceStatic(false);
|
|
62
59
|
}
|
|
63
|
-
}, [
|
|
64
|
-
messages,
|
|
65
|
-
isLoading,
|
|
66
|
-
isCommandRunning,
|
|
67
|
-
isCompressing,
|
|
68
|
-
isExpanded,
|
|
69
|
-
isConfirmationVisible,
|
|
70
|
-
isConfirmationTooTall,
|
|
71
|
-
stdout?.rows,
|
|
72
|
-
]);
|
|
73
|
-
|
|
74
|
-
const displayMessages = messages;
|
|
60
|
+
}, [isConfirmationVisible, terminalHeight]);
|
|
75
61
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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();
|
|
80
67
|
}
|
|
81
|
-
|
|
82
|
-
}, [
|
|
83
|
-
isConfirmationTooTall,
|
|
84
|
-
originalHandleConfirmationCancel,
|
|
85
|
-
setWasLastDetailsTooTall,
|
|
86
|
-
]);
|
|
87
|
-
|
|
88
|
-
const wrappedHandleConfirmationDecision = useCallback(
|
|
89
|
-
(decision: PermissionDecision) => {
|
|
90
|
-
if (isConfirmationTooTall) {
|
|
91
|
-
setWasLastDetailsTooTall((prev) => prev + 1);
|
|
92
|
-
setIsConfirmationTooTall(false);
|
|
93
|
-
}
|
|
94
|
-
handleConfirmationDecision(decision);
|
|
95
|
-
},
|
|
96
|
-
[
|
|
97
|
-
isConfirmationTooTall,
|
|
98
|
-
handleConfirmationDecision,
|
|
99
|
-
setWasLastDetailsTooTall,
|
|
100
|
-
],
|
|
101
|
-
);
|
|
68
|
+
prevForceStatic.current = forceStatic;
|
|
69
|
+
}, [forceStatic, hasPendingConfirmations, requestRemount]);
|
|
102
70
|
|
|
103
71
|
if (!sessionId) return null;
|
|
104
72
|
|
|
105
73
|
return (
|
|
106
|
-
<Box ref={
|
|
74
|
+
<Box ref={chatInterfaceRef} flexDirection="column">
|
|
107
75
|
<MessageList
|
|
76
|
+
key={remountKey}
|
|
108
77
|
messages={displayMessages}
|
|
109
78
|
isExpanded={isExpanded}
|
|
110
|
-
forceStatic={isConfirmationVisible && isConfirmationTooTall}
|
|
111
79
|
version={version}
|
|
112
80
|
workdir={workdir}
|
|
81
|
+
forceStatic={forceStatic}
|
|
113
82
|
/>
|
|
114
83
|
|
|
115
84
|
{!isConfirmationVisible && !isExpanded && (
|
|
@@ -141,20 +110,32 @@ export const ChatInterface: React.FC = () => {
|
|
|
141
110
|
|
|
142
111
|
{isConfirmationVisible && (
|
|
143
112
|
<>
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
+
)}
|
|
151
132
|
<ConfirmationSelector
|
|
152
133
|
toolName={confirmingTool!.name}
|
|
153
134
|
toolInput={confirmingTool!.input}
|
|
154
135
|
suggestedPrefix={confirmingTool!.suggestedPrefix}
|
|
155
136
|
hidePersistentOption={confirmingTool!.hidePersistentOption}
|
|
156
137
|
isExpanded={isExpanded}
|
|
157
|
-
onDecision={
|
|
138
|
+
onDecision={handleConfirmationDecision}
|
|
158
139
|
onCancel={handleConfirmationCancel}
|
|
159
140
|
onAbort={abortMessage}
|
|
160
141
|
/>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Box, Text
|
|
2
|
+
import { Box, Text } 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
|
-
isStatic?: boolean;
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
export const ConfirmationDetails: React.FC<ConfirmationDetailsProps> = ({
|
|
@@ -49,7 +48,6 @@ export const ConfirmationDetails: React.FC<ConfirmationDetailsProps> = ({
|
|
|
49
48
|
toolInput,
|
|
50
49
|
planContent,
|
|
51
50
|
isExpanded = false,
|
|
52
|
-
isStatic = false,
|
|
53
51
|
}) => {
|
|
54
52
|
const startLineNumber =
|
|
55
53
|
(toolInput?.startLineNumber as number | undefined) ??
|
|
@@ -96,14 +94,6 @@ export const ConfirmationDetails: React.FC<ConfirmationDetailsProps> = ({
|
|
|
96
94
|
</Box>
|
|
97
95
|
);
|
|
98
96
|
|
|
99
|
-
if (isStatic) {
|
|
100
|
-
return (
|
|
101
|
-
<Static items={[1]}>
|
|
102
|
-
{(item) => <React.Fragment key={item}>{content}</React.Fragment>}
|
|
103
|
-
</Static>
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
97
|
return content;
|
|
108
98
|
};
|
|
109
99
|
|
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,15 +106,15 @@ 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[];
|
|
113
116
|
sessionIds: string[];
|
|
114
117
|
}>;
|
|
115
|
-
wasLastDetailsTooTall: number;
|
|
116
|
-
setWasLastDetailsTooTall: React.Dispatch<React.SetStateAction<number>>;
|
|
117
118
|
// Status metadata
|
|
118
119
|
getGatewayConfig: () => import("wave-agent-sdk").GatewayConfig;
|
|
119
120
|
getModelConfig: () => import("wave-agent-sdk").ModelConfig;
|
|
@@ -154,6 +155,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
154
155
|
model,
|
|
155
156
|
}) => {
|
|
156
157
|
const { restoreSessionId, continueLastSession } = useAppConfig();
|
|
158
|
+
const { stdout } = useStdout();
|
|
157
159
|
|
|
158
160
|
// Message Display State
|
|
159
161
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
@@ -252,8 +254,36 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
252
254
|
reject: () => void;
|
|
253
255
|
} | null>(null);
|
|
254
256
|
|
|
255
|
-
//
|
|
256
|
-
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
|
+
}, []);
|
|
257
287
|
|
|
258
288
|
// Status metadata state
|
|
259
289
|
const [workingDirectory, setWorkingDirectory] = useState("");
|
|
@@ -280,8 +310,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
280
310
|
[],
|
|
281
311
|
);
|
|
282
312
|
|
|
283
|
-
// Confirmation too tall state
|
|
284
|
-
const [wasLastDetailsTooTall, setWasLastDetailsTooTall] = useState(0);
|
|
285
313
|
const allowBypassInCycle =
|
|
286
314
|
!!bypassPermissions || initialPermissionMode === "bypassPermissions";
|
|
287
315
|
|
|
@@ -631,9 +659,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
631
659
|
if (agentRef.current) {
|
|
632
660
|
try {
|
|
633
661
|
await agentRef.current.truncateHistory(index);
|
|
634
|
-
|
|
635
|
-
// Clear terminal screen after rewind
|
|
636
|
-
setRewindId((prev) => prev + 1);
|
|
662
|
+
setRemountKey((prev) => prev + 1);
|
|
637
663
|
} catch (error) {
|
|
638
664
|
logger.error("Failed to rewind:", error);
|
|
639
665
|
}
|
|
@@ -753,17 +779,18 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
753
779
|
setPermissionMode,
|
|
754
780
|
allowBypassInCycle,
|
|
755
781
|
isConfirmationVisible,
|
|
782
|
+
hasPendingConfirmations: confirmationQueue.length > 0,
|
|
756
783
|
confirmingTool,
|
|
757
784
|
showConfirmation,
|
|
758
785
|
hideConfirmation,
|
|
759
786
|
handleConfirmationDecision,
|
|
760
787
|
handleConfirmationCancel,
|
|
761
788
|
backgroundCurrentTask,
|
|
762
|
-
|
|
789
|
+
remountKey,
|
|
790
|
+
requestRemount,
|
|
763
791
|
handleRewindSelect,
|
|
764
792
|
getFullMessageThread,
|
|
765
|
-
|
|
766
|
-
setWasLastDetailsTooTall,
|
|
793
|
+
|
|
767
794
|
getGatewayConfig,
|
|
768
795
|
getModelConfig,
|
|
769
796
|
workingDirectory,
|