wave-code 0.19.9 → 1.0.1
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 +20 -1
- package/dist/components/App.js +7 -0
- package/dist/components/BtwDisplay.js +13 -3
- package/dist/components/ChatInterface.js +21 -6
- package/dist/components/HelpView.js +6 -0
- package/dist/components/InputBox.d.ts +1 -2
- package/dist/components/InputBox.js +16 -5
- package/dist/components/LoadingIndicator.d.ts +1 -2
- package/dist/components/LoadingIndicator.js +2 -2
- package/dist/components/Markdown.js +13 -16
- package/dist/components/MessageList.d.ts +2 -1
- package/dist/components/MessageList.js +2 -2
- package/dist/components/RewindCommand.js +4 -2
- package/dist/components/StatusLine.d.ts +0 -2
- package/dist/components/StatusLine.js +6 -6
- package/dist/components/TaskList.js +2 -1
- package/dist/components/ToolDisplay.d.ts +1 -0
- package/dist/components/ToolDisplay.js +17 -9
- package/dist/constants/commands.js +0 -6
- package/dist/contexts/useChat.d.ts +3 -5
- package/dist/contexts/useChat.js +242 -82
- package/dist/daemon-cli.d.ts +10 -0
- package/dist/daemon-cli.js +15 -0
- package/dist/hooks/useInputManager.d.ts +1 -0
- package/dist/hooks/useInputManager.js +120 -40
- package/dist/index.js +10 -0
- package/dist/managers/inputHandlers.js +55 -30
- package/dist/managers/inputReducer.d.ts +22 -22
- package/dist/managers/inputReducer.js +361 -177
- package/dist/print-cli.js +36 -10
- package/dist/stdio/agentBridge.d.ts +23 -0
- package/dist/stdio/agentBridge.js +151 -18
- package/dist/stdio/daemonServer.d.ts +67 -0
- package/dist/stdio/daemonServer.js +191 -0
- package/dist/stdio/index.d.ts +2 -0
- package/dist/stdio/index.js +2 -0
- package/dist/stdio/jsonRpcConnection.d.ts +30 -0
- package/dist/stdio/jsonRpcConnection.js +127 -0
- package/dist/stdio/protocol.d.ts +2 -2
- package/dist/stdio/stdioServer.d.ts +2 -7
- package/dist/stdio/stdioServer.js +9 -100
- package/dist/utils/bracketedPaste.d.ts +39 -0
- package/dist/utils/bracketedPaste.js +122 -0
- package/dist/utils/markdownTable.d.ts +34 -0
- package/dist/utils/markdownTable.js +302 -0
- package/dist/utils/rewindCheckpoints.d.ts +8 -0
- package/dist/utils/rewindCheckpoints.js +15 -0
- package/dist/utils/throttle.d.ts +3 -3
- package/dist/utils/worktree.d.ts +8 -0
- package/dist/utils/worktree.js +32 -1
- package/package.json +4 -2
- package/src/cli.tsx +20 -1
- package/src/components/App.tsx +5 -0
- package/src/components/BtwDisplay.tsx +36 -12
- package/src/components/ChatInterface.tsx +26 -11
- package/src/components/HelpView.tsx +6 -0
- package/src/components/InputBox.tsx +21 -10
- package/src/components/LoadingIndicator.tsx +1 -4
- package/src/components/Markdown.tsx +15 -18
- package/src/components/MessageList.tsx +6 -0
- package/src/components/RewindCommand.tsx +4 -2
- package/src/components/StatusLine.tsx +0 -10
- package/src/components/TaskList.tsx +2 -1
- package/src/components/ToolDisplay.tsx +17 -6
- package/src/constants/commands.ts +0 -6
- package/src/contexts/useChat.tsx +310 -95
- package/src/daemon-cli.ts +17 -0
- package/src/hooks/useInputManager.ts +135 -43
- package/src/index.ts +12 -0
- package/src/managers/inputHandlers.ts +55 -32
- package/src/managers/inputReducer.ts +442 -214
- package/src/print-cli.ts +48 -11
- package/src/stdio/agentBridge.ts +213 -18
- package/src/stdio/daemonServer.ts +212 -0
- package/src/stdio/index.ts +2 -0
- package/src/stdio/jsonRpcConnection.ts +160 -0
- package/src/stdio/protocol.ts +5 -2
- package/src/stdio/stdioServer.ts +14 -120
- package/src/utils/bracketedPaste.ts +170 -0
- package/src/utils/markdownTable.ts +359 -0
- package/src/utils/rewindCheckpoints.ts +15 -0
- package/src/utils/throttle.ts +8 -8
- package/src/utils/worktree.ts +50 -1
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
import { useEffect, useReducer, useCallback } from "react";
|
|
2
|
-
import { inputReducer, initialState, } from "../managers/inputReducer.js";
|
|
1
|
+
import { useEffect, useReducer, useCallback, useRef } from "react";
|
|
2
|
+
import { inputReducer, initialState, ESC_DOUBLE_PRESS_TIMEOUT_MS, btwOverlayActiveRef, } from "../managers/inputReducer.js";
|
|
3
|
+
import { createBracketedPasteDetector } from "../utils/bracketedPaste.js";
|
|
3
4
|
import { searchFiles as searchFilesUtil, PromptHistoryManager, } from "wave-agent-sdk";
|
|
4
5
|
import * as handlers from "../managers/inputHandlers.js";
|
|
5
6
|
export const useInputManager = (callbacks = {}) => {
|
|
6
7
|
const [state, dispatch] = useReducer(inputReducer, initialState);
|
|
7
|
-
|
|
8
|
+
// Detects bracketed paste (DECSET 2004) markers so pasted text is inserted
|
|
9
|
+
// without triggering submit — a pasted trailing \r must not be treated as
|
|
10
|
+
// Enter (see utils/bracketedPaste.ts).
|
|
11
|
+
const pasteDetectorRef = useRef(createBracketedPasteDetector());
|
|
12
|
+
// Abort plumbing for the /btw side question: ABORT_BTW (set when the overlay
|
|
13
|
+
// is dismissed while loading) aborts the in-flight onAskBtw call; the
|
|
14
|
+
// dismissed ref suppresses the late SET_BTW_STATE dispatch / error logging.
|
|
15
|
+
const btwAbortRef = useRef(null);
|
|
16
|
+
const btwDismissedRef = useRef(false);
|
|
17
|
+
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, isIdle: isIdleProp, } = callbacks;
|
|
8
18
|
// Handle debounced file search
|
|
9
19
|
useEffect(() => {
|
|
10
20
|
if (state.showFileSelector) {
|
|
@@ -24,22 +34,16 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
24
34
|
return () => clearTimeout(timer);
|
|
25
35
|
}
|
|
26
36
|
}, [state.showFileSelector, state.fileSearchQuery]);
|
|
27
|
-
//
|
|
37
|
+
// Auto-expire the double-Esc clear pending flag after the timeout window
|
|
38
|
+
// (aligned with Claude Code's useDoublePress timeout-based expiry).
|
|
28
39
|
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]);
|
|
40
|
+
if (!state.escClearPending)
|
|
41
|
+
return;
|
|
42
|
+
const timer = setTimeout(() => {
|
|
43
|
+
dispatch({ type: "RESET_ESC_CLEAR_PENDING" });
|
|
44
|
+
}, ESC_DOUBLE_PRESS_TIMEOUT_MS);
|
|
45
|
+
return () => clearTimeout(timer);
|
|
46
|
+
}, [state.escClearPending]);
|
|
43
47
|
// Sync state changes with callbacks
|
|
44
48
|
useEffect(() => {
|
|
45
49
|
onInputTextChange?.(state.inputText);
|
|
@@ -65,28 +69,57 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
65
69
|
case "ABORT_MESSAGE":
|
|
66
70
|
onAbortMessage?.();
|
|
67
71
|
break;
|
|
72
|
+
case "SAVE_PROMPT_HISTORY":
|
|
73
|
+
PromptHistoryManager.addEntry(effect.content, sessionId, effect.longTextMap, workdir).catch((err) => {
|
|
74
|
+
logger?.error("Failed to save prompt history", err);
|
|
75
|
+
});
|
|
76
|
+
break;
|
|
68
77
|
case "BACKGROUND_CURRENT_TASK":
|
|
69
78
|
onBackgroundCurrentTask?.();
|
|
70
79
|
break;
|
|
71
|
-
case "ASK_BTW":
|
|
80
|
+
case "ASK_BTW": {
|
|
81
|
+
const controller = new AbortController();
|
|
82
|
+
btwAbortRef.current = controller;
|
|
83
|
+
btwDismissedRef.current = false;
|
|
72
84
|
try {
|
|
73
|
-
const answer = await onAskBtw?.(effect.question)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
85
|
+
const answer = await onAskBtw?.(effect.question, controller.signal, (content) => {
|
|
86
|
+
// Stream partial answers into the overlay so the user sees
|
|
87
|
+
// the response grow in real time (same visual language as
|
|
88
|
+
// assistant text / thinking blocks) instead of a static
|
|
89
|
+
// "Answering..." indicator.
|
|
90
|
+
if (!btwDismissedRef.current) {
|
|
91
|
+
dispatch({
|
|
92
|
+
type: "SET_BTW_STATE",
|
|
93
|
+
payload: { answer: content, isLoading: true },
|
|
94
|
+
});
|
|
95
|
+
}
|
|
77
96
|
});
|
|
97
|
+
if (!btwDismissedRef.current) {
|
|
98
|
+
dispatch({
|
|
99
|
+
type: "SET_BTW_STATE",
|
|
100
|
+
payload: { answer, isLoading: false },
|
|
101
|
+
});
|
|
102
|
+
}
|
|
78
103
|
}
|
|
79
104
|
catch (error) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
105
|
+
if (!btwDismissedRef.current) {
|
|
106
|
+
console.error("Failed to ask side question:", error);
|
|
107
|
+
dispatch({
|
|
108
|
+
type: "SET_BTW_STATE",
|
|
109
|
+
payload: {
|
|
110
|
+
answer: "Error: Failed to get an answer for your side question.",
|
|
111
|
+
isLoading: false,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
}
|
|
88
115
|
}
|
|
89
116
|
break;
|
|
117
|
+
}
|
|
118
|
+
case "ABORT_BTW":
|
|
119
|
+
btwDismissedRef.current = true;
|
|
120
|
+
btwAbortRef.current?.abort();
|
|
121
|
+
btwAbortRef.current = null;
|
|
122
|
+
break;
|
|
90
123
|
case "PERMISSION_MODE_CHANGE":
|
|
91
124
|
onPermissionModeChange?.(effect.mode);
|
|
92
125
|
break;
|
|
@@ -156,6 +189,18 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
156
189
|
else if (command === "logout") {
|
|
157
190
|
dispatch({ type: "SET_SHOW_LOGIN_COMMAND", payload: true });
|
|
158
191
|
}
|
|
192
|
+
else if (command === "btw") {
|
|
193
|
+
// Bare /btw executed via the command selector — show usage
|
|
194
|
+
// (aligned with Claude Code's empty-args message).
|
|
195
|
+
dispatch({
|
|
196
|
+
type: "SET_BTW_STATE",
|
|
197
|
+
payload: {
|
|
198
|
+
question: "",
|
|
199
|
+
isLoading: false,
|
|
200
|
+
answer: "Usage: /btw <your question>",
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
}
|
|
159
204
|
else if (command === "plugin") {
|
|
160
205
|
dispatch({ type: "SET_SHOW_PLUGIN_MANAGER", payload: true });
|
|
161
206
|
}
|
|
@@ -171,9 +216,6 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
171
216
|
else if (command === "compact") {
|
|
172
217
|
await onCompact?.(effect.args);
|
|
173
218
|
}
|
|
174
|
-
else if (command === "goal") {
|
|
175
|
-
await onGoalCommand?.(effect.args);
|
|
176
|
-
}
|
|
177
219
|
}
|
|
178
220
|
break;
|
|
179
221
|
case "RECALL_QUEUED_MESSAGE":
|
|
@@ -201,7 +243,6 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
201
243
|
onRecallQueuedMessage,
|
|
202
244
|
onClearMessages,
|
|
203
245
|
onCompact,
|
|
204
|
-
onGoalCommand,
|
|
205
246
|
]);
|
|
206
247
|
useEffect(() => {
|
|
207
248
|
onFileSelectorStateChange?.(state.showFileSelector, state.filteredFiles, state.fileSearchQuery, state.atPosition);
|
|
@@ -254,7 +295,13 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
254
295
|
useEffect(() => {
|
|
255
296
|
onImagesStateChange?.(state.attachedImages);
|
|
256
297
|
}, [state.attachedImages, onImagesStateChange]);
|
|
257
|
-
//
|
|
298
|
+
// Keep the shared overlay-active flag in sync so App's Ctrl+C exit handler
|
|
299
|
+
// can defer to the /btw overlay (ink runs ALL useInput handlers per keypress
|
|
300
|
+
// with no propagation control).
|
|
301
|
+
useEffect(() => {
|
|
302
|
+
btwOverlayActiveRef.current =
|
|
303
|
+
state.btwState.question !== "" || state.btwState.answer !== undefined;
|
|
304
|
+
}, [state.btwState.question, state.btwState.answer]);
|
|
258
305
|
// Methods
|
|
259
306
|
const insertTextAtCursor = useCallback((text) => {
|
|
260
307
|
dispatch({ type: "INSERT_TEXT", payload: text });
|
|
@@ -375,9 +422,10 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
375
422
|
key: {},
|
|
376
423
|
hasSlashCommand: (cmd) => !!onHasSlashCommand?.(cmd),
|
|
377
424
|
hasQueuedMessages: hasQueuedMessagesProp ?? false,
|
|
425
|
+
isIdle: isIdleProp ?? false,
|
|
378
426
|
},
|
|
379
427
|
});
|
|
380
|
-
}, [onHasSlashCommand, hasQueuedMessagesProp]);
|
|
428
|
+
}, [onHasSlashCommand, hasQueuedMessagesProp, isIdleProp]);
|
|
381
429
|
const handleSubmit = useCallback(async () => {
|
|
382
430
|
dispatch({
|
|
383
431
|
type: "HANDLE_KEY",
|
|
@@ -386,9 +434,10 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
386
434
|
key: { return: true },
|
|
387
435
|
hasSlashCommand: (cmd) => !!onHasSlashCommand?.(cmd),
|
|
388
436
|
hasQueuedMessages: hasQueuedMessagesProp ?? false,
|
|
437
|
+
isIdle: isIdleProp ?? false,
|
|
389
438
|
},
|
|
390
439
|
});
|
|
391
|
-
}, [onHasSlashCommand, hasQueuedMessagesProp]);
|
|
440
|
+
}, [onHasSlashCommand, hasQueuedMessagesProp, isIdleProp]);
|
|
392
441
|
const expandLongTextPlaceholders = useCallback((text) => {
|
|
393
442
|
return handlers.expandLongTextPlaceholders(text, state.longTextMap);
|
|
394
443
|
}, [state.longTextMap]);
|
|
@@ -396,17 +445,47 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
396
445
|
dispatch({ type: "CLEAR_LONG_TEXT_MAP" });
|
|
397
446
|
}, []);
|
|
398
447
|
const handleInput = useCallback(async (input, key) => {
|
|
448
|
+
const result = pasteDetectorRef.current.process(input);
|
|
449
|
+
if (result.kind === "consume") {
|
|
450
|
+
// Content of an in-flight bracketed paste (or an empty paste):
|
|
451
|
+
// hold it, never submit or insert prematurely.
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
if (result.kind === "paste") {
|
|
455
|
+
if (result.leadingInput) {
|
|
456
|
+
dispatch({
|
|
457
|
+
type: "HANDLE_KEY",
|
|
458
|
+
payload: {
|
|
459
|
+
input: result.leadingInput,
|
|
460
|
+
key,
|
|
461
|
+
hasSlashCommand: (cmd) => !!onHasSlashCommand?.(cmd),
|
|
462
|
+
hasQueuedMessages: hasQueuedMessagesProp ?? false,
|
|
463
|
+
isIdle: isIdleProp ?? false,
|
|
464
|
+
},
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
if (result.text !== "") {
|
|
468
|
+
// Insert-only: \r → \n normalizes CRLF terminals, matching the
|
|
469
|
+
// canonical paste path (inputHandlers.handlePasteInput).
|
|
470
|
+
dispatch({
|
|
471
|
+
type: "INSERT_TEXT_WITH_PLACEHOLDER",
|
|
472
|
+
payload: result.text.replace(/\r/g, "\n"),
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
399
477
|
dispatch({
|
|
400
478
|
type: "HANDLE_KEY",
|
|
401
479
|
payload: {
|
|
402
|
-
input,
|
|
480
|
+
input: result.input,
|
|
403
481
|
key,
|
|
404
482
|
hasSlashCommand: (cmd) => !!onHasSlashCommand?.(cmd),
|
|
405
483
|
hasQueuedMessages: hasQueuedMessagesProp ?? false,
|
|
484
|
+
isIdle: isIdleProp ?? false,
|
|
406
485
|
},
|
|
407
486
|
});
|
|
408
487
|
return true;
|
|
409
|
-
}, [onHasSlashCommand, hasQueuedMessagesProp]);
|
|
488
|
+
}, [onHasSlashCommand, hasQueuedMessagesProp, isIdleProp]);
|
|
410
489
|
return {
|
|
411
490
|
// State
|
|
412
491
|
inputText: state.inputText,
|
|
@@ -433,6 +512,7 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
433
512
|
permissionMode: state.permissionMode,
|
|
434
513
|
attachedImages: state.attachedImages,
|
|
435
514
|
btwState: state.btwState,
|
|
515
|
+
escClearPending: state.escClearPending,
|
|
436
516
|
isManagerReady: true,
|
|
437
517
|
// Methods
|
|
438
518
|
insertTextAtCursor,
|
package/dist/index.js
CHANGED
|
@@ -44,6 +44,11 @@ export async function main() {
|
|
|
44
44
|
type: "boolean",
|
|
45
45
|
default: false,
|
|
46
46
|
global: false,
|
|
47
|
+
})
|
|
48
|
+
.option("daemon", {
|
|
49
|
+
description: "Start as a background daemon (JSON-RPC over a unix socket at PATH)",
|
|
50
|
+
type: "string",
|
|
51
|
+
global: false,
|
|
47
52
|
})
|
|
48
53
|
.option("show-stats", {
|
|
49
54
|
description: "Show timing and usage statistics in print mode",
|
|
@@ -300,6 +305,11 @@ export async function main() {
|
|
|
300
305
|
const { startStdioCli } = await import("./stdio-cli.js");
|
|
301
306
|
return startStdioCli();
|
|
302
307
|
}
|
|
308
|
+
// Handle daemon mode (remote background sessions)
|
|
309
|
+
if (typeof argv.daemon === "string") {
|
|
310
|
+
const { startDaemonCli } = await import("./daemon-cli.js");
|
|
311
|
+
return startDaemonCli(argv.daemon);
|
|
312
|
+
}
|
|
303
313
|
await startCli({
|
|
304
314
|
restoreSessionId: argv.restore,
|
|
305
315
|
continueLastSession: argv.continue,
|
|
@@ -33,15 +33,27 @@ export const handleSubmit = async (state, dispatch, callbacks, attachedImagesOve
|
|
|
33
33
|
const question = contentWithPlaceholders.startsWith("/btw ")
|
|
34
34
|
? contentWithPlaceholders.substring(5).trim()
|
|
35
35
|
: "";
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
if (question) {
|
|
37
|
+
dispatch({
|
|
38
|
+
type: "SET_BTW_STATE",
|
|
39
|
+
payload: {
|
|
40
|
+
question,
|
|
41
|
+
isLoading: true,
|
|
42
|
+
answer: undefined,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
// Bare /btw — show usage (aligned with Claude Code)
|
|
48
|
+
dispatch({
|
|
49
|
+
type: "SET_BTW_STATE",
|
|
50
|
+
payload: {
|
|
51
|
+
question: "",
|
|
52
|
+
isLoading: false,
|
|
53
|
+
answer: "Usage: /btw <your question>",
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
45
57
|
dispatch({ type: "CLEAR_INPUT" });
|
|
46
58
|
dispatch({ type: "RESET_HISTORY_NAVIGATION" });
|
|
47
59
|
dispatch({ type: "CLEAR_LONG_TEXT_MAP" });
|
|
@@ -196,15 +208,12 @@ export const processSelectorInput = (state, dispatch, char) => {
|
|
|
196
208
|
};
|
|
197
209
|
export const handlePasteInput = (state, dispatch, callbacks, input) => {
|
|
198
210
|
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
|
|
211
|
+
if (inputString.length > 1) {
|
|
212
|
+
// Multi-char chunk: insert immediately (\r → \n normalizes CRLF
|
|
213
|
+
// terminals), matching the reducer's HANDLE_KEY path.
|
|
205
214
|
dispatch({
|
|
206
|
-
type: "
|
|
207
|
-
payload:
|
|
215
|
+
type: "INSERT_TEXT_WITH_PLACEHOLDER",
|
|
216
|
+
payload: inputString.replace(/\r/g, "\n"),
|
|
208
217
|
});
|
|
209
218
|
}
|
|
210
219
|
else {
|
|
@@ -264,6 +273,18 @@ export const handleCommandSelect = (state, dispatch, callbacks, command) => {
|
|
|
264
273
|
else if (command === "status") {
|
|
265
274
|
dispatch({ type: "SET_SHOW_STATUS_COMMAND", payload: true });
|
|
266
275
|
}
|
|
276
|
+
else if (command === "btw") {
|
|
277
|
+
// Bare /btw executed via the command selector — show usage
|
|
278
|
+
// (aligned with Claude Code's empty-args message).
|
|
279
|
+
dispatch({
|
|
280
|
+
type: "SET_BTW_STATE",
|
|
281
|
+
payload: {
|
|
282
|
+
question: "",
|
|
283
|
+
isLoading: false,
|
|
284
|
+
answer: "Usage: /btw <your question>",
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
}
|
|
267
288
|
else if (command === "plugin") {
|
|
268
289
|
dispatch({ type: "SET_SHOW_PLUGIN_MANAGER", payload: true });
|
|
269
290
|
}
|
|
@@ -279,9 +300,6 @@ export const handleCommandSelect = (state, dispatch, callbacks, command) => {
|
|
|
279
300
|
else if (command === "compact") {
|
|
280
301
|
await callbacks.onCompact?.();
|
|
281
302
|
}
|
|
282
|
-
else if (command === "goal") {
|
|
283
|
-
await callbacks.onGoalCommand?.();
|
|
284
|
-
}
|
|
285
303
|
}
|
|
286
304
|
})();
|
|
287
305
|
dispatch({ type: "CANCEL_COMMAND_SELECTOR" });
|
|
@@ -510,16 +528,23 @@ export const handleNormalInput = async (state, dispatch, callbacks, input, key,
|
|
|
510
528
|
return false;
|
|
511
529
|
};
|
|
512
530
|
export const handleInput = async (state, dispatch, callbacks, input, key, clearImages) => {
|
|
513
|
-
//
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
531
|
+
// /btw overlay handling (mirrors inputReducer's HANDLE_KEY block; this
|
|
532
|
+
// handler is not wired, kept in sync for consistency). Active while a
|
|
533
|
+
// question is displayed, or the bare-/btw usage message shows. Only
|
|
534
|
+
// Escape dismisses; every other key is ignored.
|
|
535
|
+
if (state.btwState.question || state.btwState.answer) {
|
|
536
|
+
if (key.escape) {
|
|
537
|
+
dispatch({
|
|
538
|
+
type: "SET_BTW_STATE",
|
|
539
|
+
payload: {
|
|
540
|
+
question: "",
|
|
541
|
+
answer: undefined,
|
|
542
|
+
isLoading: false,
|
|
543
|
+
},
|
|
544
|
+
});
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
547
|
+
// Any other key while the overlay is up is ignored
|
|
523
548
|
return true;
|
|
524
549
|
}
|
|
525
550
|
if (key.escape) {
|
|
@@ -10,6 +10,16 @@ export interface BtwState {
|
|
|
10
10
|
answer?: string;
|
|
11
11
|
isLoading: boolean;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* True while the /btw overlay is up (a question is on display, loading or
|
|
15
|
+
* answered). App's Ctrl+C exit handler checks this so Ctrl+C does not quit
|
|
16
|
+
* the app while the overlay owns the keys. Synced from useInputManager via
|
|
17
|
+
* an effect.
|
|
18
|
+
*/
|
|
19
|
+
export declare const btwOverlayActiveRef: {
|
|
20
|
+
current: boolean;
|
|
21
|
+
};
|
|
22
|
+
export declare const ESC_DOUBLE_PRESS_TIMEOUT_MS = 1000;
|
|
13
23
|
export type PendingEffect = {
|
|
14
24
|
type: "SEND_MESSAGE";
|
|
15
25
|
content: string;
|
|
@@ -18,6 +28,10 @@ export type PendingEffect = {
|
|
|
18
28
|
mimeType: string;
|
|
19
29
|
}>;
|
|
20
30
|
longTextMap: Record<string, string>;
|
|
31
|
+
} | {
|
|
32
|
+
type: "SAVE_PROMPT_HISTORY";
|
|
33
|
+
content: string;
|
|
34
|
+
longTextMap: Record<string, string>;
|
|
21
35
|
} | {
|
|
22
36
|
type: "ABORT_MESSAGE";
|
|
23
37
|
} | {
|
|
@@ -25,6 +39,8 @@ export type PendingEffect = {
|
|
|
25
39
|
} | {
|
|
26
40
|
type: "ASK_BTW";
|
|
27
41
|
question: string;
|
|
42
|
+
} | {
|
|
43
|
+
type: "ABORT_BTW";
|
|
28
44
|
} | {
|
|
29
45
|
type: "PERMISSION_MODE_CHANGE";
|
|
30
46
|
mode: PermissionMode;
|
|
@@ -62,10 +78,9 @@ export interface InputManagerCallbacks {
|
|
|
62
78
|
onAbortMessage?: () => void;
|
|
63
79
|
onBackgroundCurrentTask?: () => void;
|
|
64
80
|
onPermissionModeChange?: (mode: PermissionMode) => void;
|
|
65
|
-
onAskBtw?: (question: string) => Promise<string>;
|
|
81
|
+
onAskBtw?: (question: string, abortSignal?: AbortSignal, onContent?: (content: string) => void) => Promise<string>;
|
|
66
82
|
onClearMessages?: () => Promise<void>;
|
|
67
83
|
onCompact?: (instructions?: string) => Promise<void>;
|
|
68
|
-
onGoalCommand?: (args?: string) => Promise<void>;
|
|
69
84
|
sessionId?: string;
|
|
70
85
|
workdir?: string;
|
|
71
86
|
getFullMessageThread?: () => Promise<{
|
|
@@ -74,6 +89,7 @@ export interface InputManagerCallbacks {
|
|
|
74
89
|
}>;
|
|
75
90
|
logger?: Logger;
|
|
76
91
|
hasQueuedMessages?: boolean;
|
|
92
|
+
isIdle?: boolean;
|
|
77
93
|
onRecallQueuedMessage?: () => void;
|
|
78
94
|
}
|
|
79
95
|
export interface InputState {
|
|
@@ -103,9 +119,6 @@ export interface InputState {
|
|
|
103
119
|
showWorkflowManager: boolean;
|
|
104
120
|
permissionMode: PermissionMode;
|
|
105
121
|
selectorJustUsed: boolean;
|
|
106
|
-
isPasting: boolean;
|
|
107
|
-
pasteBuffer: string;
|
|
108
|
-
initialPasteCursorPosition: number;
|
|
109
122
|
history: PromptEntry[];
|
|
110
123
|
historyIndex: number;
|
|
111
124
|
originalInputText: string;
|
|
@@ -113,6 +126,7 @@ export interface InputState {
|
|
|
113
126
|
isFileSearching: boolean;
|
|
114
127
|
btwState: BtwState;
|
|
115
128
|
pendingEffect: PendingEffect | null;
|
|
129
|
+
escClearPending: boolean;
|
|
116
130
|
}
|
|
117
131
|
export declare const initialState: InputState;
|
|
118
132
|
export type InputAction = {
|
|
@@ -206,23 +220,6 @@ export type InputAction = {
|
|
|
206
220
|
type: "CLEAR_LONG_TEXT_MAP";
|
|
207
221
|
} | {
|
|
208
222
|
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
223
|
} | {
|
|
227
224
|
type: "ADD_IMAGE_AND_INSERT_PLACEHOLDER";
|
|
228
225
|
payload: {
|
|
@@ -254,6 +251,8 @@ export type InputAction = {
|
|
|
254
251
|
payload: Partial<BtwState>;
|
|
255
252
|
} | {
|
|
256
253
|
type: "CLEAR_PENDING_EFFECT";
|
|
254
|
+
} | {
|
|
255
|
+
type: "RESET_ESC_CLEAR_PENDING";
|
|
257
256
|
} | {
|
|
258
257
|
type: "HANDLE_KEY";
|
|
259
258
|
payload: {
|
|
@@ -261,6 +260,7 @@ export type InputAction = {
|
|
|
261
260
|
key: Key;
|
|
262
261
|
hasSlashCommand: (cmd: string) => boolean;
|
|
263
262
|
hasQueuedMessages?: boolean;
|
|
263
|
+
isIdle?: boolean;
|
|
264
264
|
};
|
|
265
265
|
};
|
|
266
266
|
export declare function inputReducer(state: InputState, action: InputAction): InputState;
|