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,13 @@
|
|
|
1
|
-
import { useEffect, useReducer, useCallback } from "react";
|
|
1
|
+
import { useEffect, useReducer, useCallback, useRef } from "react";
|
|
2
2
|
import { Key } from "ink";
|
|
3
3
|
import {
|
|
4
4
|
inputReducer,
|
|
5
5
|
initialState,
|
|
6
6
|
InputManagerCallbacks,
|
|
7
|
+
ESC_DOUBLE_PRESS_TIMEOUT_MS,
|
|
8
|
+
btwOverlayActiveRef,
|
|
7
9
|
} from "../managers/inputReducer.js";
|
|
10
|
+
import { createBracketedPasteDetector } from "../utils/bracketedPaste.js";
|
|
8
11
|
import {
|
|
9
12
|
searchFiles as searchFilesUtil,
|
|
10
13
|
PermissionMode,
|
|
@@ -18,6 +21,17 @@ export const useInputManager = (
|
|
|
18
21
|
) => {
|
|
19
22
|
const [state, dispatch] = useReducer(inputReducer, initialState);
|
|
20
23
|
|
|
24
|
+
// Detects bracketed paste (DECSET 2004) markers so pasted text is inserted
|
|
25
|
+
// without triggering submit — a pasted trailing \r must not be treated as
|
|
26
|
+
// Enter (see utils/bracketedPaste.ts).
|
|
27
|
+
const pasteDetectorRef = useRef(createBracketedPasteDetector());
|
|
28
|
+
|
|
29
|
+
// Abort plumbing for the /btw side question: ABORT_BTW (set when the overlay
|
|
30
|
+
// is dismissed while loading) aborts the in-flight onAskBtw call; the
|
|
31
|
+
// dismissed ref suppresses the late SET_BTW_STATE dispatch / error logging.
|
|
32
|
+
const btwAbortRef = useRef<AbortController | null>(null);
|
|
33
|
+
const btwDismissedRef = useRef(false);
|
|
34
|
+
|
|
21
35
|
const {
|
|
22
36
|
onInputTextChange,
|
|
23
37
|
onCursorPositionChange,
|
|
@@ -47,7 +61,7 @@ export const useInputManager = (
|
|
|
47
61
|
onRecallQueuedMessage,
|
|
48
62
|
onClearMessages,
|
|
49
63
|
onCompact,
|
|
50
|
-
|
|
64
|
+
isIdle: isIdleProp,
|
|
51
65
|
} = callbacks;
|
|
52
66
|
|
|
53
67
|
// Handle debounced file search
|
|
@@ -70,25 +84,15 @@ export const useInputManager = (
|
|
|
70
84
|
}
|
|
71
85
|
}, [state.showFileSelector, state.fileSearchQuery]);
|
|
72
86
|
|
|
73
|
-
//
|
|
87
|
+
// Auto-expire the double-Esc clear pending flag after the timeout window
|
|
88
|
+
// (aligned with Claude Code's useDoublePress timeout-based expiry).
|
|
74
89
|
useEffect(() => {
|
|
75
|
-
if (state.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const processedInput = state.pasteBuffer.replace(/\r/g, "\n");
|
|
82
|
-
dispatch({
|
|
83
|
-
type: "INSERT_TEXT_WITH_PLACEHOLDER",
|
|
84
|
-
payload: processedInput,
|
|
85
|
-
});
|
|
86
|
-
dispatch({ type: "END_PASTE" });
|
|
87
|
-
dispatch({ type: "RESET_HISTORY_NAVIGATION" });
|
|
88
|
-
}, pasteDebounceDelay);
|
|
89
|
-
return () => clearTimeout(timer);
|
|
90
|
-
}
|
|
91
|
-
}, [state.isPasting, state.pasteBuffer]);
|
|
90
|
+
if (!state.escClearPending) return;
|
|
91
|
+
const timer = setTimeout(() => {
|
|
92
|
+
dispatch({ type: "RESET_ESC_CLEAR_PENDING" });
|
|
93
|
+
}, ESC_DOUBLE_PRESS_TIMEOUT_MS);
|
|
94
|
+
return () => clearTimeout(timer);
|
|
95
|
+
}, [state.escClearPending]);
|
|
92
96
|
|
|
93
97
|
// Sync state changes with callbacks
|
|
94
98
|
useEffect(() => {
|
|
@@ -127,28 +131,66 @@ export const useInputManager = (
|
|
|
127
131
|
case "ABORT_MESSAGE":
|
|
128
132
|
onAbortMessage?.();
|
|
129
133
|
break;
|
|
134
|
+
case "SAVE_PROMPT_HISTORY":
|
|
135
|
+
PromptHistoryManager.addEntry(
|
|
136
|
+
effect.content,
|
|
137
|
+
sessionId,
|
|
138
|
+
effect.longTextMap,
|
|
139
|
+
workdir,
|
|
140
|
+
).catch((err: unknown) => {
|
|
141
|
+
logger?.error("Failed to save prompt history", err);
|
|
142
|
+
});
|
|
143
|
+
break;
|
|
130
144
|
case "BACKGROUND_CURRENT_TASK":
|
|
131
145
|
onBackgroundCurrentTask?.();
|
|
132
146
|
break;
|
|
133
|
-
case "ASK_BTW":
|
|
147
|
+
case "ASK_BTW": {
|
|
148
|
+
const controller = new AbortController();
|
|
149
|
+
btwAbortRef.current = controller;
|
|
150
|
+
btwDismissedRef.current = false;
|
|
134
151
|
try {
|
|
135
|
-
const answer = await onAskBtw?.(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
152
|
+
const answer = await onAskBtw?.(
|
|
153
|
+
effect.question,
|
|
154
|
+
controller.signal,
|
|
155
|
+
(content: string) => {
|
|
156
|
+
// Stream partial answers into the overlay so the user sees
|
|
157
|
+
// the response grow in real time (same visual language as
|
|
158
|
+
// assistant text / thinking blocks) instead of a static
|
|
159
|
+
// "Answering..." indicator.
|
|
160
|
+
if (!btwDismissedRef.current) {
|
|
161
|
+
dispatch({
|
|
162
|
+
type: "SET_BTW_STATE",
|
|
163
|
+
payload: { answer: content, isLoading: true },
|
|
164
|
+
});
|
|
165
|
+
}
|
|
148
166
|
},
|
|
149
|
-
|
|
167
|
+
);
|
|
168
|
+
if (!btwDismissedRef.current) {
|
|
169
|
+
dispatch({
|
|
170
|
+
type: "SET_BTW_STATE",
|
|
171
|
+
payload: { answer, isLoading: false },
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (!btwDismissedRef.current) {
|
|
176
|
+
console.error("Failed to ask side question:", error);
|
|
177
|
+
dispatch({
|
|
178
|
+
type: "SET_BTW_STATE",
|
|
179
|
+
payload: {
|
|
180
|
+
answer:
|
|
181
|
+
"Error: Failed to get an answer for your side question.",
|
|
182
|
+
isLoading: false,
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
}
|
|
150
186
|
}
|
|
151
187
|
break;
|
|
188
|
+
}
|
|
189
|
+
case "ABORT_BTW":
|
|
190
|
+
btwDismissedRef.current = true;
|
|
191
|
+
btwAbortRef.current?.abort();
|
|
192
|
+
btwAbortRef.current = null;
|
|
193
|
+
break;
|
|
152
194
|
case "PERMISSION_MODE_CHANGE":
|
|
153
195
|
onPermissionModeChange?.(effect.mode);
|
|
154
196
|
break;
|
|
@@ -209,6 +251,17 @@ export const useInputManager = (
|
|
|
209
251
|
dispatch({ type: "SET_SHOW_LOGIN_COMMAND", payload: true });
|
|
210
252
|
} else if (command === "logout") {
|
|
211
253
|
dispatch({ type: "SET_SHOW_LOGIN_COMMAND", payload: true });
|
|
254
|
+
} else if (command === "btw") {
|
|
255
|
+
// Bare /btw executed via the command selector — show usage
|
|
256
|
+
// (aligned with Claude Code's empty-args message).
|
|
257
|
+
dispatch({
|
|
258
|
+
type: "SET_BTW_STATE",
|
|
259
|
+
payload: {
|
|
260
|
+
question: "",
|
|
261
|
+
isLoading: false,
|
|
262
|
+
answer: "Usage: /btw <your question>",
|
|
263
|
+
},
|
|
264
|
+
});
|
|
212
265
|
} else if (command === "plugin") {
|
|
213
266
|
dispatch({ type: "SET_SHOW_PLUGIN_MANAGER", payload: true });
|
|
214
267
|
} else if (command === "model") {
|
|
@@ -219,8 +272,6 @@ export const useInputManager = (
|
|
|
219
272
|
await onClearMessages?.();
|
|
220
273
|
} else if (command === "compact") {
|
|
221
274
|
await onCompact?.(effect.args);
|
|
222
|
-
} else if (command === "goal") {
|
|
223
|
-
await onGoalCommand?.(effect.args);
|
|
224
275
|
}
|
|
225
276
|
}
|
|
226
277
|
break;
|
|
@@ -249,7 +300,6 @@ export const useInputManager = (
|
|
|
249
300
|
onRecallQueuedMessage,
|
|
250
301
|
onClearMessages,
|
|
251
302
|
onCompact,
|
|
252
|
-
onGoalCommand,
|
|
253
303
|
]);
|
|
254
304
|
|
|
255
305
|
useEffect(() => {
|
|
@@ -327,7 +377,13 @@ export const useInputManager = (
|
|
|
327
377
|
onImagesStateChange?.(state.attachedImages);
|
|
328
378
|
}, [state.attachedImages, onImagesStateChange]);
|
|
329
379
|
|
|
330
|
-
//
|
|
380
|
+
// Keep the shared overlay-active flag in sync so App's Ctrl+C exit handler
|
|
381
|
+
// can defer to the /btw overlay (ink runs ALL useInput handlers per keypress
|
|
382
|
+
// with no propagation control).
|
|
383
|
+
useEffect(() => {
|
|
384
|
+
btwOverlayActiveRef.current =
|
|
385
|
+
state.btwState.question !== "" || state.btwState.answer !== undefined;
|
|
386
|
+
}, [state.btwState.question, state.btwState.answer]);
|
|
331
387
|
|
|
332
388
|
// Methods
|
|
333
389
|
const insertTextAtCursor = useCallback((text: string) => {
|
|
@@ -501,10 +557,11 @@ export const useInputManager = (
|
|
|
501
557
|
key: {} as Key,
|
|
502
558
|
hasSlashCommand: (cmd) => !!onHasSlashCommand?.(cmd),
|
|
503
559
|
hasQueuedMessages: hasQueuedMessagesProp ?? false,
|
|
560
|
+
isIdle: isIdleProp ?? false,
|
|
504
561
|
},
|
|
505
562
|
});
|
|
506
563
|
},
|
|
507
|
-
[onHasSlashCommand, hasQueuedMessagesProp],
|
|
564
|
+
[onHasSlashCommand, hasQueuedMessagesProp, isIdleProp],
|
|
508
565
|
);
|
|
509
566
|
|
|
510
567
|
const handleSubmit = useCallback(async () => {
|
|
@@ -515,9 +572,10 @@ export const useInputManager = (
|
|
|
515
572
|
key: { return: true } as Key,
|
|
516
573
|
hasSlashCommand: (cmd) => !!onHasSlashCommand?.(cmd),
|
|
517
574
|
hasQueuedMessages: hasQueuedMessagesProp ?? false,
|
|
575
|
+
isIdle: isIdleProp ?? false,
|
|
518
576
|
},
|
|
519
577
|
});
|
|
520
|
-
}, [onHasSlashCommand, hasQueuedMessagesProp]);
|
|
578
|
+
}, [onHasSlashCommand, hasQueuedMessagesProp, isIdleProp]);
|
|
521
579
|
|
|
522
580
|
const expandLongTextPlaceholders = useCallback(
|
|
523
581
|
(text: string) => {
|
|
@@ -532,18 +590,51 @@ export const useInputManager = (
|
|
|
532
590
|
|
|
533
591
|
const handleInput = useCallback(
|
|
534
592
|
async (input: string, key: Key) => {
|
|
593
|
+
const result = pasteDetectorRef.current.process(input);
|
|
594
|
+
|
|
595
|
+
if (result.kind === "consume") {
|
|
596
|
+
// Content of an in-flight bracketed paste (or an empty paste):
|
|
597
|
+
// hold it, never submit or insert prematurely.
|
|
598
|
+
return true;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (result.kind === "paste") {
|
|
602
|
+
if (result.leadingInput) {
|
|
603
|
+
dispatch({
|
|
604
|
+
type: "HANDLE_KEY",
|
|
605
|
+
payload: {
|
|
606
|
+
input: result.leadingInput,
|
|
607
|
+
key,
|
|
608
|
+
hasSlashCommand: (cmd) => !!onHasSlashCommand?.(cmd),
|
|
609
|
+
hasQueuedMessages: hasQueuedMessagesProp ?? false,
|
|
610
|
+
isIdle: isIdleProp ?? false,
|
|
611
|
+
},
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
if (result.text !== "") {
|
|
615
|
+
// Insert-only: \r → \n normalizes CRLF terminals, matching the
|
|
616
|
+
// canonical paste path (inputHandlers.handlePasteInput).
|
|
617
|
+
dispatch({
|
|
618
|
+
type: "INSERT_TEXT_WITH_PLACEHOLDER",
|
|
619
|
+
payload: result.text.replace(/\r/g, "\n"),
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
return true;
|
|
623
|
+
}
|
|
624
|
+
|
|
535
625
|
dispatch({
|
|
536
626
|
type: "HANDLE_KEY",
|
|
537
627
|
payload: {
|
|
538
|
-
input,
|
|
628
|
+
input: result.input,
|
|
539
629
|
key,
|
|
540
630
|
hasSlashCommand: (cmd) => !!onHasSlashCommand?.(cmd),
|
|
541
631
|
hasQueuedMessages: hasQueuedMessagesProp ?? false,
|
|
632
|
+
isIdle: isIdleProp ?? false,
|
|
542
633
|
},
|
|
543
634
|
});
|
|
544
635
|
return true;
|
|
545
636
|
},
|
|
546
|
-
[onHasSlashCommand, hasQueuedMessagesProp],
|
|
637
|
+
[onHasSlashCommand, hasQueuedMessagesProp, isIdleProp],
|
|
547
638
|
);
|
|
548
639
|
|
|
549
640
|
return {
|
|
@@ -572,6 +663,7 @@ export const useInputManager = (
|
|
|
572
663
|
permissionMode: state.permissionMode,
|
|
573
664
|
attachedImages: state.attachedImages,
|
|
574
665
|
btwState: state.btwState,
|
|
666
|
+
escClearPending: state.escClearPending,
|
|
575
667
|
isManagerReady: true,
|
|
576
668
|
|
|
577
669
|
// Methods
|
package/src/index.ts
CHANGED
|
@@ -53,6 +53,12 @@ export async function main() {
|
|
|
53
53
|
default: false,
|
|
54
54
|
global: false,
|
|
55
55
|
})
|
|
56
|
+
.option("daemon", {
|
|
57
|
+
description:
|
|
58
|
+
"Start as a background daemon (JSON-RPC over a unix socket at PATH)",
|
|
59
|
+
type: "string",
|
|
60
|
+
global: false,
|
|
61
|
+
})
|
|
56
62
|
.option("show-stats", {
|
|
57
63
|
description: "Show timing and usage statistics in print mode",
|
|
58
64
|
type: "boolean",
|
|
@@ -402,6 +408,12 @@ export async function main() {
|
|
|
402
408
|
return startStdioCli();
|
|
403
409
|
}
|
|
404
410
|
|
|
411
|
+
// Handle daemon mode (remote background sessions)
|
|
412
|
+
if (typeof argv.daemon === "string") {
|
|
413
|
+
const { startDaemonCli } = await import("./daemon-cli.js");
|
|
414
|
+
return startDaemonCli(argv.daemon);
|
|
415
|
+
}
|
|
416
|
+
|
|
405
417
|
await startCli({
|
|
406
418
|
restoreSessionId: argv.restore as string | undefined,
|
|
407
419
|
continueLastSession: argv.continue as boolean | undefined,
|
|
@@ -63,16 +63,26 @@ export const handleSubmit = async (
|
|
|
63
63
|
? contentWithPlaceholders.substring(5).trim()
|
|
64
64
|
: "";
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
66
|
+
if (question) {
|
|
67
|
+
dispatch({
|
|
68
|
+
type: "SET_BTW_STATE",
|
|
69
|
+
payload: {
|
|
70
|
+
question,
|
|
71
|
+
isLoading: true,
|
|
72
|
+
answer: undefined,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
} else {
|
|
76
|
+
// Bare /btw — show usage (aligned with Claude Code)
|
|
77
|
+
dispatch({
|
|
78
|
+
type: "SET_BTW_STATE",
|
|
79
|
+
payload: {
|
|
80
|
+
question: "",
|
|
81
|
+
isLoading: false,
|
|
82
|
+
answer: "Usage: /btw <your question>",
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
76
86
|
dispatch({ type: "CLEAR_INPUT" });
|
|
77
87
|
dispatch({ type: "RESET_HISTORY_NAVIGATION" });
|
|
78
88
|
dispatch({ type: "CLEAR_LONG_TEXT_MAP" });
|
|
@@ -292,17 +302,13 @@ export const handlePasteInput = (
|
|
|
292
302
|
input: string,
|
|
293
303
|
): void => {
|
|
294
304
|
const inputString = input;
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
if (isPasteOperation) {
|
|
301
|
-
// Dispatch a single action type; the reducer determines start vs append
|
|
302
|
-
// by checking pasteBuffer, avoiding stale state issues
|
|
305
|
+
|
|
306
|
+
if (inputString.length > 1) {
|
|
307
|
+
// Multi-char chunk: insert immediately (\r → \n normalizes CRLF
|
|
308
|
+
// terminals), matching the reducer's HANDLE_KEY path.
|
|
303
309
|
dispatch({
|
|
304
|
-
type: "
|
|
305
|
-
payload:
|
|
310
|
+
type: "INSERT_TEXT_WITH_PLACEHOLDER",
|
|
311
|
+
payload: inputString.replace(/\r/g, "\n"),
|
|
306
312
|
});
|
|
307
313
|
} else {
|
|
308
314
|
let char = inputString;
|
|
@@ -367,6 +373,17 @@ export const handleCommandSelect = (
|
|
|
367
373
|
dispatch({ type: "SET_SHOW_HELP", payload: true });
|
|
368
374
|
} else if (command === "status") {
|
|
369
375
|
dispatch({ type: "SET_SHOW_STATUS_COMMAND", payload: true });
|
|
376
|
+
} else if (command === "btw") {
|
|
377
|
+
// Bare /btw executed via the command selector — show usage
|
|
378
|
+
// (aligned with Claude Code's empty-args message).
|
|
379
|
+
dispatch({
|
|
380
|
+
type: "SET_BTW_STATE",
|
|
381
|
+
payload: {
|
|
382
|
+
question: "",
|
|
383
|
+
isLoading: false,
|
|
384
|
+
answer: "Usage: /btw <your question>",
|
|
385
|
+
},
|
|
386
|
+
});
|
|
370
387
|
} else if (command === "plugin") {
|
|
371
388
|
dispatch({ type: "SET_SHOW_PLUGIN_MANAGER", payload: true });
|
|
372
389
|
} else if (command === "model") {
|
|
@@ -377,8 +394,6 @@ export const handleCommandSelect = (
|
|
|
377
394
|
await callbacks.onClearMessages?.();
|
|
378
395
|
} else if (command === "compact") {
|
|
379
396
|
await callbacks.onCompact?.();
|
|
380
|
-
} else if (command === "goal") {
|
|
381
|
-
await callbacks.onGoalCommand?.();
|
|
382
397
|
}
|
|
383
398
|
}
|
|
384
399
|
})();
|
|
@@ -702,16 +717,24 @@ export const handleInput = async (
|
|
|
702
717
|
key: Key,
|
|
703
718
|
clearImages?: () => void,
|
|
704
719
|
): Promise<boolean> => {
|
|
705
|
-
//
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
720
|
+
// /btw overlay handling (mirrors inputReducer's HANDLE_KEY block; this
|
|
721
|
+
// handler is not wired, kept in sync for consistency). Active while a
|
|
722
|
+
// question is displayed, or the bare-/btw usage message shows. Only
|
|
723
|
+
// Escape dismisses; every other key is ignored.
|
|
724
|
+
if (state.btwState.question || state.btwState.answer) {
|
|
725
|
+
if (key.escape) {
|
|
726
|
+
dispatch({
|
|
727
|
+
type: "SET_BTW_STATE",
|
|
728
|
+
payload: {
|
|
729
|
+
question: "",
|
|
730
|
+
answer: undefined,
|
|
731
|
+
isLoading: false,
|
|
732
|
+
},
|
|
733
|
+
});
|
|
734
|
+
return true;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Any other key while the overlay is up is ignored
|
|
715
738
|
return true;
|
|
716
739
|
}
|
|
717
740
|
|