wave-code 0.19.8 → 1.0.0
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/cli.js +1 -1
- package/dist/components/ChatInterface.js +1 -1
- package/dist/components/HelpView.js +6 -0
- package/dist/components/InputBox.d.ts +2 -0
- package/dist/components/InputBox.js +11 -2
- package/dist/components/RewindCommand.js +4 -2
- package/dist/components/TaskList.js +28 -12
- package/dist/hooks/useInputManager.d.ts +1 -0
- package/dist/hooks/useInputManager.js +23 -20
- package/dist/index.js +1 -1
- package/dist/managers/inputHandlers.js +5 -8
- package/dist/managers/inputReducer.d.ts +10 -20
- package/dist/managers/inputReducer.js +309 -173
- package/dist/print-cli.js +36 -10
- package/dist/stdio/agentBridge.d.ts +6 -0
- package/dist/stdio/agentBridge.js +141 -1
- package/dist/stdio/protocol.d.ts +1 -1
- package/dist/utils/rewindCheckpoints.d.ts +8 -0
- package/dist/utils/rewindCheckpoints.js +15 -0
- package/dist/utils/worktree.d.ts +12 -2
- package/dist/utils/worktree.js +61 -25
- package/package.json +2 -2
- package/src/cli.tsx +1 -1
- package/src/components/ChatInterface.tsx +2 -0
- package/src/components/HelpView.tsx +6 -0
- package/src/components/InputBox.tsx +19 -0
- package/src/components/RewindCommand.tsx +4 -2
- package/src/components/TaskList.tsx +30 -12
- package/src/hooks/useInputManager.ts +27 -21
- package/src/index.ts +1 -1
- package/src/managers/inputHandlers.ts +6 -10
- package/src/managers/inputReducer.ts +381 -208
- package/src/print-cli.ts +48 -11
- package/src/stdio/agentBridge.ts +210 -0
- package/src/stdio/protocol.ts +6 -1
- package/src/utils/rewindCheckpoints.ts +15 -0
- package/src/utils/worktree.ts +99 -34
package/dist/cli.js
CHANGED
|
@@ -23,7 +23,7 @@ export async function startCli(options) {
|
|
|
23
23
|
// Cleanup worktree if requested
|
|
24
24
|
if (shouldRemoveWorktree && worktreeSession) {
|
|
25
25
|
process.chdir(worktreeSession.repoRoot);
|
|
26
|
-
removeWorktree(worktreeSession);
|
|
26
|
+
await removeWorktree(worktreeSession);
|
|
27
27
|
}
|
|
28
28
|
process.exit(0);
|
|
29
29
|
}
|
|
@@ -40,5 +40,5 @@ export const ChatInterface = () => {
|
|
|
40
40
|
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: [(isLoading ||
|
|
41
41
|
isCommandRunning ||
|
|
42
42
|
isCompacting ||
|
|
43
|
-
isGoalEvaluating) && (_jsx(LoadingIndicator, { isLoading: isLoading, isCommandRunning: isCommandRunning, isCompacting: isCompacting, isGoalEvaluating: isGoalEvaluating, 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, latestTotalTokens: latestTotalTokens, maxInputTokens: maxInputTokens, isGoalActive: isGoalActive, goalElapsed: goalElapsed })] })), 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 }, "confirmation-details")) })) : (_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 })] }))] }));
|
|
43
|
+
isGoalEvaluating) && (_jsx(LoadingIndicator, { isLoading: isLoading, isCommandRunning: isCommandRunning, isCompacting: isCompacting, isGoalEvaluating: isGoalEvaluating, latestTotalTokens: latestTotalTokens })), _jsx(TaskList, {}), _jsx(QueuedMessageList, {}), _jsx(InputBox, { isLoading: isLoading, isCommandRunning: isCommandRunning, isCompacting: isCompacting, isGoalEvaluating: isGoalEvaluating, sendMessage: sendMessage, abortMessage: abortMessage, mcpServers: mcpServers, connectMcpServer: connectMcpServer, disconnectMcpServer: disconnectMcpServer, slashCommands: slashCommands, hasSlashCommand: hasSlashCommand, latestTotalTokens: latestTotalTokens, maxInputTokens: maxInputTokens, isGoalActive: isGoalActive, goalElapsed: goalElapsed })] })), 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 }, "confirmation-details")) })) : (_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 })] }))] }));
|
|
44
44
|
};
|
|
@@ -47,11 +47,17 @@ export const HelpView = ({ onCancel, commands = [], }) => {
|
|
|
47
47
|
{ key: "Ctrl+B", description: "Background current task" },
|
|
48
48
|
{ key: "Ctrl+V", description: "Paste image" },
|
|
49
49
|
{ key: "Ctrl+J", description: "Newline" },
|
|
50
|
+
{ key: "Ctrl+A", description: "Cursor to line start" },
|
|
51
|
+
{ key: "Ctrl+E", description: "Cursor to line end" },
|
|
52
|
+
{ key: "Ctrl+U", description: "Delete to line start" },
|
|
53
|
+
{ key: "Ctrl+K", description: "Delete to line end" },
|
|
54
|
+
{ key: "Ctrl+W", description: "Delete word before cursor" },
|
|
50
55
|
{ key: "Shift+Tab", description: "Cycle permission mode" },
|
|
51
56
|
{
|
|
52
57
|
key: "Esc",
|
|
53
58
|
description: "Interrupt AI or command / Cancel selector / Close help",
|
|
54
59
|
},
|
|
60
|
+
{ key: "Esc ×2", description: "Clear input (idle)" },
|
|
55
61
|
];
|
|
56
62
|
const footerText = [
|
|
57
63
|
"Tab switch",
|
|
@@ -5,6 +5,8 @@ export declare const INPUT_PLACEHOLDER_TEXT_PREFIX: string;
|
|
|
5
5
|
export interface InputBoxProps {
|
|
6
6
|
isLoading?: boolean;
|
|
7
7
|
isCommandRunning?: boolean;
|
|
8
|
+
isCompacting?: boolean;
|
|
9
|
+
isGoalEvaluating?: boolean;
|
|
8
10
|
workdir?: string;
|
|
9
11
|
sendMessage?: (message: string, images?: Array<{
|
|
10
12
|
path: string;
|
|
@@ -20,11 +20,17 @@ import { useInputManager } from "../hooks/useInputManager.js";
|
|
|
20
20
|
import { useChat } from "../contexts/useChat.js";
|
|
21
21
|
export const INPUT_PLACEHOLDER_TEXT = "Type your message (use /help for more info)...";
|
|
22
22
|
export const INPUT_PLACEHOLDER_TEXT_PREFIX = INPUT_PLACEHOLDER_TEXT.substring(0, 10);
|
|
23
|
-
export const InputBox = ({ sendMessage = () => { }, abortMessage = () => { }, mcpServers = [], connectMcpServer = async () => false, disconnectMcpServer = async () => false, slashCommands = [], hasSlashCommand = () => false, latestTotalTokens = 0, maxInputTokens = 200000, isGoalActive, goalElapsed, }) => {
|
|
23
|
+
export const InputBox = ({ isLoading, isCommandRunning, isCompacting, isGoalEvaluating, sendMessage = () => { }, abortMessage = () => { }, mcpServers = [], connectMcpServer = async () => false, disconnectMcpServer = async () => false, slashCommands = [], hasSlashCommand = () => false, latestTotalTokens = 0, maxInputTokens = 200000, isGoalActive, goalElapsed, }) => {
|
|
24
24
|
const { permissionMode: chatPermissionMode, setPermissionMode: setChatPermissionMode, handleRewindSelect, backgroundCurrentTask, messages, getFullMessageThread, sessionId, workingDirectory, askBtw, clearMessages, compact, goalCommand, currentModel, configuredModels, setModel, recreateAgent, recallQueuedMessage, queuedMessages, } = useChat();
|
|
25
25
|
// Ref to hold setInputText so queue callbacks can access it before useInputManager returns
|
|
26
26
|
const setInputTextRef = useRef(() => { });
|
|
27
27
|
const hasQueuedMessages = (queuedMessages?.length ?? 0) > 0;
|
|
28
|
+
// Idle means no AI work in flight. Esc double-press clear only applies when
|
|
29
|
+
// idle; while busy, Esc keeps its abort semantics.
|
|
30
|
+
const isIdle = !(isLoading ||
|
|
31
|
+
isCommandRunning ||
|
|
32
|
+
isCompacting ||
|
|
33
|
+
isGoalEvaluating);
|
|
28
34
|
const onRecallQueuedMessage = useCallback(() => {
|
|
29
35
|
const msg = recallQueuedMessage();
|
|
30
36
|
if (msg) {
|
|
@@ -46,6 +52,8 @@ export const InputBox = ({ sendMessage = () => { }, abortMessage = () => { }, mc
|
|
|
46
52
|
permissionMode, setPermissionMode,
|
|
47
53
|
// BTW state
|
|
48
54
|
btwState,
|
|
55
|
+
// Esc double-press clear pending
|
|
56
|
+
escClearPending,
|
|
49
57
|
// Main handler
|
|
50
58
|
handleInput,
|
|
51
59
|
// Manager ready state
|
|
@@ -65,6 +73,7 @@ export const InputBox = ({ sendMessage = () => { }, abortMessage = () => { }, mc
|
|
|
65
73
|
workdir: workingDirectory,
|
|
66
74
|
getFullMessageThread,
|
|
67
75
|
hasQueuedMessages,
|
|
76
|
+
isIdle,
|
|
68
77
|
onRecallQueuedMessage,
|
|
69
78
|
});
|
|
70
79
|
// Keep setInputText ref updated for queue callbacks
|
|
@@ -146,5 +155,5 @@ export const InputBox = ({ sendMessage = () => { }, abortMessage = () => { }, mc
|
|
|
146
155
|
showStatusCommand ||
|
|
147
156
|
showLoginCommand ||
|
|
148
157
|
showPluginManager ||
|
|
149
|
-
showWorkflowManager || (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { borderStyle: "single", borderColor: "gray", borderLeft: false, borderRight: false, children: _jsx(Text, { color: isPlaceholder ? "gray" : "white", children: shouldShowCursor ? (_jsxs(_Fragment, { children: [beforeCursor, _jsx(Text, { backgroundColor: "white", color: "black", children: atCursor }), afterCursor] })) : (displayText) }) }), _jsx(StatusLine, { permissionMode: permissionMode, isShellCommand: isShellCommand, isGoalActive: isGoalActive, goalElapsed: goalElapsed, latestTotalTokens: latestTotalTokens, maxInputTokens: maxInputTokens })] }))] }));
|
|
158
|
+
showWorkflowManager || (_jsxs(Box, { flexDirection: "column", children: [escClearPending && _jsx(Text, { color: "gray", children: "\u518D\u6B21\u6309 Esc \u6E05\u7A7A\u8F93\u5165" }), _jsx(Box, { borderStyle: "single", borderColor: "gray", borderLeft: false, borderRight: false, children: _jsx(Text, { color: isPlaceholder ? "gray" : "white", children: shouldShowCursor ? (_jsxs(_Fragment, { children: [beforeCursor, _jsx(Text, { backgroundColor: "white", color: "black", children: atCursor }), afterCursor] })) : (displayText) }) }), _jsx(StatusLine, { permissionMode: permissionMode, isShellCommand: isShellCommand, isGoalActive: isGoalActive, goalElapsed: goalElapsed, latestTotalTokens: latestTotalTokens, maxInputTokens: maxInputTokens })] }))] }));
|
|
150
159
|
};
|
|
@@ -3,6 +3,7 @@ import { useState, useReducer, useEffect } from "react";
|
|
|
3
3
|
import { Box, Text, useInput } from "ink";
|
|
4
4
|
import { getMessageContent } from "wave-agent-sdk";
|
|
5
5
|
import { rewindSelectorReducer } from "../reducers/rewindSelectorReducer.js";
|
|
6
|
+
import { isUserCheckpointMessage } from "../utils/rewindCheckpoints.js";
|
|
6
7
|
export const RewindCommand = ({ messages: initialMessages, onSelect, onCancel, getFullMessageThread, }) => {
|
|
7
8
|
const [messages, setMessages] = useState(initialMessages);
|
|
8
9
|
const [isLoading, setIsLoading] = useState(!!getFullMessageThread);
|
|
@@ -14,10 +15,11 @@ export const RewindCommand = ({ messages: initialMessages, onSelect, onCancel, g
|
|
|
14
15
|
});
|
|
15
16
|
}
|
|
16
17
|
}, [getFullMessageThread]);
|
|
17
|
-
// Filter user messages as checkpoints, excluding meta messages
|
|
18
|
+
// Filter user messages as checkpoints, excluding meta messages and
|
|
19
|
+
// system-generated user-role messages (task notifications, hook injections)
|
|
18
20
|
const checkpoints = messages
|
|
19
21
|
.map((msg, index) => ({ msg, index }))
|
|
20
|
-
.filter(({ msg }) => msg
|
|
22
|
+
.filter(({ msg }) => isUserCheckpointMessage(msg));
|
|
21
23
|
const MAX_VISIBLE_ITEMS = 3;
|
|
22
24
|
const [state, dispatch] = useReducer(rewindSelectorReducer, {
|
|
23
25
|
selectedIndex: checkpoints.length - 1,
|
|
@@ -80,12 +80,9 @@ export const TaskList = () => {
|
|
|
80
80
|
const completionTimestampsRef = React.useRef(new Map());
|
|
81
81
|
const previousCompletedIdsRef = React.useRef(null);
|
|
82
82
|
const autoHideTimerRef = React.useRef(null);
|
|
83
|
-
const [autoHidden, setAutoHidden] = React.useState(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const active = tasks.filter((t) => t.status !== "deleted");
|
|
87
|
-
return active.length > 0 && active.every((t) => t.status === "completed");
|
|
88
|
-
});
|
|
83
|
+
const [autoHidden, setAutoHidden] = React.useState(false);
|
|
84
|
+
// 是否在当前展示期间观察到过未完成任务;用于区分“任务在眼前完成”与“加载时已全部完成”
|
|
85
|
+
const hadIncompleteRef = React.useRef(false);
|
|
89
86
|
const [, forceUpdate] = React.useState(0);
|
|
90
87
|
const now = Date.now();
|
|
91
88
|
const activeTasks = tasks.filter((t) => t.status !== "deleted");
|
|
@@ -125,23 +122,42 @@ export const TaskList = () => {
|
|
|
125
122
|
// Auto-hide logic: when all active tasks are completed
|
|
126
123
|
const allCompleted = activeTasks.length > 0 &&
|
|
127
124
|
activeTasks.every((t) => t.status === "completed");
|
|
125
|
+
// 观察到任务从“存在未完成”变为“全部完成”时保留 5 秒再隐藏;
|
|
126
|
+
// 加载或恢复到已全部完成的会话时立即隐藏,避免先闪现再消失
|
|
128
127
|
React.useEffect(() => {
|
|
129
|
-
if (
|
|
128
|
+
if (activeTasks.length === 0) {
|
|
129
|
+
hadIncompleteRef.current = false;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (!allCompleted) {
|
|
133
|
+
hadIncompleteRef.current = true;
|
|
134
|
+
if (autoHidden) {
|
|
135
|
+
setAutoHidden(false);
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (!hadIncompleteRef.current) {
|
|
140
|
+
if (!autoHidden) {
|
|
141
|
+
setAutoHidden(true);
|
|
142
|
+
}
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (!autoHidden) {
|
|
130
146
|
autoHideTimerRef.current = setTimeout(() => {
|
|
131
147
|
setAutoHidden(true);
|
|
132
148
|
}, AUTO_HIDE_DELAY_MS);
|
|
133
149
|
}
|
|
134
|
-
if (!allCompleted && autoHidden) {
|
|
135
|
-
setAutoHidden(false);
|
|
136
|
-
}
|
|
137
150
|
return () => {
|
|
138
151
|
if (autoHideTimerRef.current) {
|
|
139
152
|
clearTimeout(autoHideTimerRef.current);
|
|
140
153
|
autoHideTimerRef.current = null;
|
|
141
154
|
}
|
|
142
155
|
};
|
|
143
|
-
}, [allCompleted, autoHidden]);
|
|
144
|
-
if (tasks.length === 0 ||
|
|
156
|
+
}, [allCompleted, autoHidden, activeTasks.length]);
|
|
157
|
+
if (tasks.length === 0 ||
|
|
158
|
+
!isTaskListVisible ||
|
|
159
|
+
autoHidden ||
|
|
160
|
+
(allCompleted && !hadIncompleteRef.current)) {
|
|
145
161
|
return null;
|
|
146
162
|
}
|
|
147
163
|
const displayLimit = getDisplayLimit(stdout?.rows);
|
|
@@ -26,6 +26,7 @@ export declare const useInputManager: (callbacks?: Partial<InputManagerCallbacks
|
|
|
26
26
|
permissionMode: PermissionMode;
|
|
27
27
|
attachedImages: import("../managers/inputReducer.js").AttachedImage[];
|
|
28
28
|
btwState: import("../managers/inputReducer.js").BtwState;
|
|
29
|
+
escClearPending: boolean;
|
|
29
30
|
isManagerReady: boolean;
|
|
30
31
|
insertTextAtCursor: (text: string) => void;
|
|
31
32
|
deleteCharAtCursor: () => void;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { useEffect, useReducer, useCallback } from "react";
|
|
2
|
-
import { inputReducer, initialState, } from "../managers/inputReducer.js";
|
|
2
|
+
import { inputReducer, initialState, ESC_DOUBLE_PRESS_TIMEOUT_MS, } from "../managers/inputReducer.js";
|
|
3
3
|
import { searchFiles as searchFilesUtil, PromptHistoryManager, } from "wave-agent-sdk";
|
|
4
4
|
import * as handlers from "../managers/inputHandlers.js";
|
|
5
5
|
export const useInputManager = (callbacks = {}) => {
|
|
6
6
|
const [state, dispatch] = useReducer(inputReducer, initialState);
|
|
7
|
-
const { onInputTextChange, onCursorPositionChange, onFileSelectorStateChange, onCommandSelectorStateChange, onHistorySearchStateChange, onBackgroundTaskManagerStateChange, onMcpManagerStateChange, onRewindManagerStateChange, onHelpStateChange, onStatusCommandStateChange, onPluginManagerStateChange, onModelSelectorStateChange, onWorkflowManagerStateChange, onImagesStateChange, onSendMessage, onHasSlashCommand, onAbortMessage, onBackgroundCurrentTask, onPermissionModeChange, onAskBtw, sessionId, workdir, getFullMessageThread, logger, hasQueuedMessages: hasQueuedMessagesProp, onRecallQueuedMessage, onClearMessages, onCompact, onGoalCommand, } = callbacks;
|
|
7
|
+
const { onInputTextChange, onCursorPositionChange, onFileSelectorStateChange, onCommandSelectorStateChange, onHistorySearchStateChange, onBackgroundTaskManagerStateChange, onMcpManagerStateChange, onRewindManagerStateChange, onHelpStateChange, onStatusCommandStateChange, onPluginManagerStateChange, onModelSelectorStateChange, onWorkflowManagerStateChange, onImagesStateChange, onSendMessage, onHasSlashCommand, onAbortMessage, onBackgroundCurrentTask, onPermissionModeChange, onAskBtw, sessionId, workdir, getFullMessageThread, logger, hasQueuedMessages: hasQueuedMessagesProp, onRecallQueuedMessage, onClearMessages, onCompact, onGoalCommand, isIdle: isIdleProp, } = callbacks;
|
|
8
8
|
// Handle debounced file search
|
|
9
9
|
useEffect(() => {
|
|
10
10
|
if (state.showFileSelector) {
|
|
@@ -24,22 +24,16 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
24
24
|
return () => clearTimeout(timer);
|
|
25
25
|
}
|
|
26
26
|
}, [state.showFileSelector, state.fileSearchQuery]);
|
|
27
|
-
//
|
|
27
|
+
// Auto-expire the double-Esc clear pending flag after the timeout window
|
|
28
|
+
// (aligned with Claude Code's useDoublePress timeout-based expiry).
|
|
28
29
|
useEffect(() => {
|
|
29
|
-
if (state.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
});
|
|
37
|
-
dispatch({ type: "END_PASTE" });
|
|
38
|
-
dispatch({ type: "RESET_HISTORY_NAVIGATION" });
|
|
39
|
-
}, pasteDebounceDelay);
|
|
40
|
-
return () => clearTimeout(timer);
|
|
41
|
-
}
|
|
42
|
-
}, [state.isPasting, state.pasteBuffer]);
|
|
30
|
+
if (!state.escClearPending)
|
|
31
|
+
return;
|
|
32
|
+
const timer = setTimeout(() => {
|
|
33
|
+
dispatch({ type: "RESET_ESC_CLEAR_PENDING" });
|
|
34
|
+
}, ESC_DOUBLE_PRESS_TIMEOUT_MS);
|
|
35
|
+
return () => clearTimeout(timer);
|
|
36
|
+
}, [state.escClearPending]);
|
|
43
37
|
// Sync state changes with callbacks
|
|
44
38
|
useEffect(() => {
|
|
45
39
|
onInputTextChange?.(state.inputText);
|
|
@@ -65,6 +59,11 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
65
59
|
case "ABORT_MESSAGE":
|
|
66
60
|
onAbortMessage?.();
|
|
67
61
|
break;
|
|
62
|
+
case "SAVE_PROMPT_HISTORY":
|
|
63
|
+
PromptHistoryManager.addEntry(effect.content, sessionId, effect.longTextMap, workdir).catch((err) => {
|
|
64
|
+
logger?.error("Failed to save prompt history", err);
|
|
65
|
+
});
|
|
66
|
+
break;
|
|
68
67
|
case "BACKGROUND_CURRENT_TASK":
|
|
69
68
|
onBackgroundCurrentTask?.();
|
|
70
69
|
break;
|
|
@@ -375,9 +374,10 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
375
374
|
key: {},
|
|
376
375
|
hasSlashCommand: (cmd) => !!onHasSlashCommand?.(cmd),
|
|
377
376
|
hasQueuedMessages: hasQueuedMessagesProp ?? false,
|
|
377
|
+
isIdle: isIdleProp ?? false,
|
|
378
378
|
},
|
|
379
379
|
});
|
|
380
|
-
}, [onHasSlashCommand, hasQueuedMessagesProp]);
|
|
380
|
+
}, [onHasSlashCommand, hasQueuedMessagesProp, isIdleProp]);
|
|
381
381
|
const handleSubmit = useCallback(async () => {
|
|
382
382
|
dispatch({
|
|
383
383
|
type: "HANDLE_KEY",
|
|
@@ -386,9 +386,10 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
386
386
|
key: { return: true },
|
|
387
387
|
hasSlashCommand: (cmd) => !!onHasSlashCommand?.(cmd),
|
|
388
388
|
hasQueuedMessages: hasQueuedMessagesProp ?? false,
|
|
389
|
+
isIdle: isIdleProp ?? false,
|
|
389
390
|
},
|
|
390
391
|
});
|
|
391
|
-
}, [onHasSlashCommand, hasQueuedMessagesProp]);
|
|
392
|
+
}, [onHasSlashCommand, hasQueuedMessagesProp, isIdleProp]);
|
|
392
393
|
const expandLongTextPlaceholders = useCallback((text) => {
|
|
393
394
|
return handlers.expandLongTextPlaceholders(text, state.longTextMap);
|
|
394
395
|
}, [state.longTextMap]);
|
|
@@ -403,10 +404,11 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
403
404
|
key,
|
|
404
405
|
hasSlashCommand: (cmd) => !!onHasSlashCommand?.(cmd),
|
|
405
406
|
hasQueuedMessages: hasQueuedMessagesProp ?? false,
|
|
407
|
+
isIdle: isIdleProp ?? false,
|
|
406
408
|
},
|
|
407
409
|
});
|
|
408
410
|
return true;
|
|
409
|
-
}, [onHasSlashCommand, hasQueuedMessagesProp]);
|
|
411
|
+
}, [onHasSlashCommand, hasQueuedMessagesProp, isIdleProp]);
|
|
410
412
|
return {
|
|
411
413
|
// State
|
|
412
414
|
inputText: state.inputText,
|
|
@@ -433,6 +435,7 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
433
435
|
permissionMode: state.permissionMode,
|
|
434
436
|
attachedImages: state.attachedImages,
|
|
435
437
|
btwState: state.btwState,
|
|
438
|
+
escClearPending: state.escClearPending,
|
|
436
439
|
isManagerReady: true,
|
|
437
440
|
// Methods
|
|
438
441
|
insertTextAtCursor,
|
package/dist/index.js
CHANGED
|
@@ -237,7 +237,7 @@ export async function main() {
|
|
|
237
237
|
name = generateRandomName();
|
|
238
238
|
}
|
|
239
239
|
const baseRef = loadMergedWaveConfig(originalCwd)?.worktree?.baseRef;
|
|
240
|
-
worktreeSession = createWorktree(name, originalCwd, { baseRef });
|
|
240
|
+
worktreeSession = await createWorktree(name, originalCwd, { baseRef });
|
|
241
241
|
// Note: the full worktree session (originalCwd etc.) is injected into the
|
|
242
242
|
// agent's DI container after the agent is created in useChat.tsx. This keeps
|
|
243
243
|
// worktree state per-session instead of process-global.
|
|
@@ -196,15 +196,12 @@ export const processSelectorInput = (state, dispatch, char) => {
|
|
|
196
196
|
};
|
|
197
197
|
export const handlePasteInput = (state, dispatch, callbacks, input) => {
|
|
198
198
|
const inputString = input;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if (isPasteOperation) {
|
|
203
|
-
// Dispatch a single action type; the reducer determines start vs append
|
|
204
|
-
// by checking pasteBuffer, avoiding stale state issues
|
|
199
|
+
if (inputString.length > 1) {
|
|
200
|
+
// Multi-char chunk: insert immediately (\r → \n normalizes CRLF
|
|
201
|
+
// terminals), matching the reducer's HANDLE_KEY path.
|
|
205
202
|
dispatch({
|
|
206
|
-
type: "
|
|
207
|
-
payload:
|
|
203
|
+
type: "INSERT_TEXT_WITH_PLACEHOLDER",
|
|
204
|
+
payload: inputString.replace(/\r/g, "\n"),
|
|
208
205
|
});
|
|
209
206
|
}
|
|
210
207
|
else {
|
|
@@ -10,6 +10,7 @@ export interface BtwState {
|
|
|
10
10
|
answer?: string;
|
|
11
11
|
isLoading: boolean;
|
|
12
12
|
}
|
|
13
|
+
export declare const ESC_DOUBLE_PRESS_TIMEOUT_MS = 1000;
|
|
13
14
|
export type PendingEffect = {
|
|
14
15
|
type: "SEND_MESSAGE";
|
|
15
16
|
content: string;
|
|
@@ -18,6 +19,10 @@ export type PendingEffect = {
|
|
|
18
19
|
mimeType: string;
|
|
19
20
|
}>;
|
|
20
21
|
longTextMap: Record<string, string>;
|
|
22
|
+
} | {
|
|
23
|
+
type: "SAVE_PROMPT_HISTORY";
|
|
24
|
+
content: string;
|
|
25
|
+
longTextMap: Record<string, string>;
|
|
21
26
|
} | {
|
|
22
27
|
type: "ABORT_MESSAGE";
|
|
23
28
|
} | {
|
|
@@ -74,6 +79,7 @@ export interface InputManagerCallbacks {
|
|
|
74
79
|
}>;
|
|
75
80
|
logger?: Logger;
|
|
76
81
|
hasQueuedMessages?: boolean;
|
|
82
|
+
isIdle?: boolean;
|
|
77
83
|
onRecallQueuedMessage?: () => void;
|
|
78
84
|
}
|
|
79
85
|
export interface InputState {
|
|
@@ -103,9 +109,6 @@ export interface InputState {
|
|
|
103
109
|
showWorkflowManager: boolean;
|
|
104
110
|
permissionMode: PermissionMode;
|
|
105
111
|
selectorJustUsed: boolean;
|
|
106
|
-
isPasting: boolean;
|
|
107
|
-
pasteBuffer: string;
|
|
108
|
-
initialPasteCursorPosition: number;
|
|
109
112
|
history: PromptEntry[];
|
|
110
113
|
historyIndex: number;
|
|
111
114
|
originalInputText: string;
|
|
@@ -113,6 +116,7 @@ export interface InputState {
|
|
|
113
116
|
isFileSearching: boolean;
|
|
114
117
|
btwState: BtwState;
|
|
115
118
|
pendingEffect: PendingEffect | null;
|
|
119
|
+
escClearPending: boolean;
|
|
116
120
|
}
|
|
117
121
|
export declare const initialState: InputState;
|
|
118
122
|
export type InputAction = {
|
|
@@ -206,23 +210,6 @@ export type InputAction = {
|
|
|
206
210
|
type: "CLEAR_LONG_TEXT_MAP";
|
|
207
211
|
} | {
|
|
208
212
|
type: "CLEAR_INPUT";
|
|
209
|
-
} | {
|
|
210
|
-
type: "START_PASTE";
|
|
211
|
-
payload: {
|
|
212
|
-
buffer: string;
|
|
213
|
-
cursorPosition: number;
|
|
214
|
-
};
|
|
215
|
-
} | {
|
|
216
|
-
type: "APPEND_PASTE_BUFFER";
|
|
217
|
-
payload: string;
|
|
218
|
-
} | {
|
|
219
|
-
type: "APPEND_PASTE_CHUNK";
|
|
220
|
-
payload: {
|
|
221
|
-
chunk: string;
|
|
222
|
-
cursorPosition: number;
|
|
223
|
-
};
|
|
224
|
-
} | {
|
|
225
|
-
type: "END_PASTE";
|
|
226
213
|
} | {
|
|
227
214
|
type: "ADD_IMAGE_AND_INSERT_PLACEHOLDER";
|
|
228
215
|
payload: {
|
|
@@ -254,6 +241,8 @@ export type InputAction = {
|
|
|
254
241
|
payload: Partial<BtwState>;
|
|
255
242
|
} | {
|
|
256
243
|
type: "CLEAR_PENDING_EFFECT";
|
|
244
|
+
} | {
|
|
245
|
+
type: "RESET_ESC_CLEAR_PENDING";
|
|
257
246
|
} | {
|
|
258
247
|
type: "HANDLE_KEY";
|
|
259
248
|
payload: {
|
|
@@ -261,6 +250,7 @@ export type InputAction = {
|
|
|
261
250
|
key: Key;
|
|
262
251
|
hasSlashCommand: (cmd: string) => boolean;
|
|
263
252
|
hasQueuedMessages?: boolean;
|
|
253
|
+
isIdle?: boolean;
|
|
264
254
|
};
|
|
265
255
|
};
|
|
266
256
|
export declare function inputReducer(state: InputState, action: InputAction): InputState;
|