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.
@@ -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,KAAmD,MAAM,OAAO,CAAC;AAWxE,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,wBAAwB,EAAE,KAAK,CAAC,EAiD5C,CAAC;AAEF,eAAO,MAAM,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC,QAAQ,CAmClC,CAAC"}
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"}
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useState, useEffect, useRef, useCallback } from "react";
3
- import { useStdout, useInput } from "ink";
2
+ import { useState, useEffect, useCallback } from "react";
3
+ import { useInput } from "ink";
4
4
  import { ChatInterface } from "./ChatInterface.js";
5
- import { ChatProvider, useChat } from "../contexts/useChat.js";
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(ChatInterfaceWithRemount, {}) }));
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,KAAyD,MAAM,OAAO,CAAC;AAc9E,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAsJjC,CAAC"}
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, useCallback, useLayoutEffect, useRef } from "react";
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 [isConfirmationTooTall, setIsConfirmationTooTall] = useState(false);
16
- const { messages, isLoading, isCommandRunning, isCompressing, sendMessage, abortMessage, mcpServers, connectMcpServer, disconnectMcpServer, isExpanded, sessionId, latestTotalTokens, slashCommands, hasSlashCommand, isConfirmationVisible, confirmingTool, handleConfirmationDecision, handleConfirmationCancel: originalHandleConfirmationCancel, setWasLastDetailsTooTall, version, workdir, btwState, } = useChat();
17
- const interfaceRef = useRef(null);
18
- useLayoutEffect(() => {
19
- if (!isConfirmationVisible) {
20
- setIsConfirmationTooTall(false);
21
- return;
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
- messages,
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
- originalHandleConfirmationCancel();
50
- }, [
51
- isConfirmationTooTall,
52
- originalHandleConfirmationCancel,
53
- setWasLastDetailsTooTall,
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
- handleConfirmationDecision(decision);
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: interfaceRef, flexDirection: "column", children: [_jsx(MessageList, { messages: displayMessages, isExpanded: isExpanded, forceStatic: isConfirmationVisible && isConfirmationTooTall, version: version, workdir: workdir }), !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, isStatic: isConfirmationTooTall }), _jsx(ConfirmationSelector, { toolName: confirmingTool.name, toolInput: confirmingTool.input, suggestedPrefix: confirmingTool.suggestedPrefix, hidePersistentOption: confirmingTool.hidePersistentOption, isExpanded: isExpanded, onDecision: wrappedHandleConfirmationDecision, 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 })] }))] }));
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;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CA6DlE,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 React from "react";
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, isStatic = 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
- rewindId: number;
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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"}
@@ -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
- // Rewind state
66
- const [rewindId, setRewindId] = useState(0);
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
- // Clear terminal screen after rewind
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
- rewindId,
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.2",
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.2"
45
+ "wave-agent-sdk": "0.12.4"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/react": "^19.1.8",
@@ -1,7 +1,7 @@
1
- import React, { useState, useEffect, useRef, useCallback } from "react";
2
- import { useStdout, useInput } from "ink";
1
+ import React, { useState, useEffect, useCallback } from "react";
2
+ import { useInput } from "ink";
3
3
  import { ChatInterface } from "./ChatInterface.js";
4
- import { ChatProvider, useChat } from "../contexts/useChat.js";
4
+ import { ChatProvider } from "../contexts/useChat.js";
5
5
  import { AppProvider } from "../contexts/useAppConfig.js";
6
6
  import { WorktreeExitPrompt } from "./WorktreeExitPrompt.js";
7
7
  import {
@@ -107,62 +107,11 @@ const AppWithProviders: React.FC<AppWithProvidersProps> = ({
107
107
  version={version}
108
108
  model={model}
109
109
  >
110
- <ChatInterfaceWithRemount />
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, useCallback, useLayoutEffect, useRef } from "react";
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: originalHandleConfirmationCancel,
38
- setWasLastDetailsTooTall,
35
+ handleConfirmationCancel,
39
36
  version,
40
37
  workdir,
41
38
  btwState,
39
+ remountKey,
40
+ requestRemount,
42
41
  } = useChat();
43
42
 
44
- const interfaceRef = useRef(null);
45
-
46
- useLayoutEffect(() => {
47
- if (!isConfirmationVisible) {
48
- setIsConfirmationTooTall(false);
49
- return;
50
- }
43
+ const displayMessages = messages;
51
44
 
52
- if (isConfirmationTooTall) {
53
- return;
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
- if (interfaceRef.current) {
57
- const { height } = measureElement(interfaceRef.current);
58
- const terminalHeight = stdout?.rows || 24;
59
- if (height > terminalHeight - 3) {
60
- setIsConfirmationTooTall(true);
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
- const handleConfirmationCancel = useCallback(() => {
77
- if (isConfirmationTooTall) {
78
- setWasLastDetailsTooTall((prev) => prev + 1);
79
- setIsConfirmationTooTall(false);
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
- originalHandleConfirmationCancel();
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={interfaceRef} flexDirection="column">
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
- <ConfirmationDetails
145
- toolName={confirmingTool!.name}
146
- toolInput={confirmingTool!.input}
147
- planContent={confirmingTool!.planContent}
148
- isExpanded={isExpanded}
149
- isStatic={isConfirmationTooTall}
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={wrappedHandleConfirmationDecision}
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, Static } from "ink";
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
 
@@ -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
- // Rewind state
256
- const [rewindId, setRewindId] = useState(0);
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
- rewindId,
789
+ remountKey,
790
+ requestRemount,
763
791
  handleRewindSelect,
764
792
  getFullMessageThread,
765
- wasLastDetailsTooTall,
766
- setWasLastDetailsTooTall,
793
+
767
794
  getGatewayConfig,
768
795
  getModelConfig,
769
796
  workingDirectory,