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.
Files changed (37) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/components/ChatInterface.js +1 -1
  3. package/dist/components/HelpView.js +6 -0
  4. package/dist/components/InputBox.d.ts +2 -0
  5. package/dist/components/InputBox.js +11 -2
  6. package/dist/components/RewindCommand.js +4 -2
  7. package/dist/components/TaskList.js +28 -12
  8. package/dist/hooks/useInputManager.d.ts +1 -0
  9. package/dist/hooks/useInputManager.js +23 -20
  10. package/dist/index.js +1 -1
  11. package/dist/managers/inputHandlers.js +5 -8
  12. package/dist/managers/inputReducer.d.ts +10 -20
  13. package/dist/managers/inputReducer.js +309 -173
  14. package/dist/print-cli.js +36 -10
  15. package/dist/stdio/agentBridge.d.ts +6 -0
  16. package/dist/stdio/agentBridge.js +141 -1
  17. package/dist/stdio/protocol.d.ts +1 -1
  18. package/dist/utils/rewindCheckpoints.d.ts +8 -0
  19. package/dist/utils/rewindCheckpoints.js +15 -0
  20. package/dist/utils/worktree.d.ts +12 -2
  21. package/dist/utils/worktree.js +61 -25
  22. package/package.json +2 -2
  23. package/src/cli.tsx +1 -1
  24. package/src/components/ChatInterface.tsx +2 -0
  25. package/src/components/HelpView.tsx +6 -0
  26. package/src/components/InputBox.tsx +19 -0
  27. package/src/components/RewindCommand.tsx +4 -2
  28. package/src/components/TaskList.tsx +30 -12
  29. package/src/hooks/useInputManager.ts +27 -21
  30. package/src/index.ts +1 -1
  31. package/src/managers/inputHandlers.ts +6 -10
  32. package/src/managers/inputReducer.ts +381 -208
  33. package/src/print-cli.ts +48 -11
  34. package/src/stdio/agentBridge.ts +210 -0
  35. package/src/stdio/protocol.ts +6 -1
  36. package/src/utils/rewindCheckpoints.ts +15 -0
  37. 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.role === "user" && !msg.isMeta);
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
- // If all tasks are already completed on mount (e.g. session restore),
85
- // start hidden immediately instead of flashing for 5 seconds.
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 (allCompleted && !autoHidden) {
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 || !isTaskListVisible || autoHidden) {
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
- // Handle paste debouncing
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.isPasting) {
30
- const pasteDebounceDelay = parseInt(process.env.PASTE_DEBOUNCE_MS || "30", 10);
31
- const timer = setTimeout(() => {
32
- const processedInput = state.pasteBuffer.replace(/\r/g, "\n");
33
- dispatch({
34
- type: "INSERT_TEXT_WITH_PLACEHOLDER",
35
- payload: processedInput,
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
- const isPasteOperation = inputString.length > 1 ||
200
- inputString.includes("\n") ||
201
- inputString.includes("\r");
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: "APPEND_PASTE_CHUNK",
207
- payload: { chunk: inputString, cursorPosition: state.cursorPosition },
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;