wave-code 1.0.0 → 1.0.2
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 +25 -8
- package/dist/components/InputBox.d.ts +1 -3
- package/dist/components/InputBox.js +12 -9
- package/dist/components/LoadingIndicator.d.ts +1 -2
- package/dist/components/LoadingIndicator.js +2 -2
- package/dist/components/LoginCommand.js +4 -2
- package/dist/components/Markdown.js +13 -16
- package/dist/components/Notifications.d.ts +7 -0
- package/dist/components/Notifications.js +9 -0
- package/dist/components/StatusLine.d.ts +0 -4
- package/dist/components/StatusLine.js +6 -10
- 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 +4 -6
- package/dist/contexts/useChat.js +253 -110
- package/dist/daemon-cli.d.ts +10 -0
- package/dist/daemon-cli.js +15 -0
- package/dist/hooks/useInputManager.js +99 -22
- package/dist/index.js +10 -0
- package/dist/managers/inputHandlers.js +50 -22
- package/dist/managers/inputReducer.d.ts +12 -2
- package/dist/managers/inputReducer.js +57 -9
- package/dist/stdio/agentBridge.d.ts +23 -0
- package/dist/stdio/agentBridge.js +134 -16
- 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/throttle.d.ts +3 -3
- 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 +30 -15
- package/src/components/InputBox.tsx +25 -24
- package/src/components/LoadingIndicator.tsx +1 -4
- package/src/components/LoginCommand.tsx +4 -2
- package/src/components/Markdown.tsx +15 -18
- package/src/components/Notifications.tsx +31 -0
- package/src/components/StatusLine.tsx +17 -44
- 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 +326 -140
- package/src/daemon-cli.ts +17 -0
- package/src/hooks/useInputManager.ts +108 -22
- package/src/index.ts +12 -0
- package/src/managers/inputHandlers.ts +49 -22
- package/src/managers/inputReducer.ts +66 -11
- package/src/stdio/agentBridge.ts +196 -17
- 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/throttle.ts +8 -8
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
import { useEffect, useReducer, useCallback } from "react";
|
|
2
|
-
import { inputReducer, initialState, ESC_DOUBLE_PRESS_TIMEOUT_MS, } 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) {
|
|
@@ -67,25 +77,49 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
67
77
|
case "BACKGROUND_CURRENT_TASK":
|
|
68
78
|
onBackgroundCurrentTask?.();
|
|
69
79
|
break;
|
|
70
|
-
case "ASK_BTW":
|
|
80
|
+
case "ASK_BTW": {
|
|
81
|
+
const controller = new AbortController();
|
|
82
|
+
btwAbortRef.current = controller;
|
|
83
|
+
btwDismissedRef.current = false;
|
|
71
84
|
try {
|
|
72
|
-
const answer = await onAskBtw?.(effect.question)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
+
}
|
|
76
96
|
});
|
|
97
|
+
if (!btwDismissedRef.current) {
|
|
98
|
+
dispatch({
|
|
99
|
+
type: "SET_BTW_STATE",
|
|
100
|
+
payload: { answer, isLoading: false },
|
|
101
|
+
});
|
|
102
|
+
}
|
|
77
103
|
}
|
|
78
104
|
catch (error) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
+
}
|
|
87
115
|
}
|
|
88
116
|
break;
|
|
117
|
+
}
|
|
118
|
+
case "ABORT_BTW":
|
|
119
|
+
btwDismissedRef.current = true;
|
|
120
|
+
btwAbortRef.current?.abort();
|
|
121
|
+
btwAbortRef.current = null;
|
|
122
|
+
break;
|
|
89
123
|
case "PERMISSION_MODE_CHANGE":
|
|
90
124
|
onPermissionModeChange?.(effect.mode);
|
|
91
125
|
break;
|
|
@@ -155,6 +189,18 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
155
189
|
else if (command === "logout") {
|
|
156
190
|
dispatch({ type: "SET_SHOW_LOGIN_COMMAND", payload: true });
|
|
157
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
|
+
}
|
|
158
204
|
else if (command === "plugin") {
|
|
159
205
|
dispatch({ type: "SET_SHOW_PLUGIN_MANAGER", payload: true });
|
|
160
206
|
}
|
|
@@ -170,9 +216,6 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
170
216
|
else if (command === "compact") {
|
|
171
217
|
await onCompact?.(effect.args);
|
|
172
218
|
}
|
|
173
|
-
else if (command === "goal") {
|
|
174
|
-
await onGoalCommand?.(effect.args);
|
|
175
|
-
}
|
|
176
219
|
}
|
|
177
220
|
break;
|
|
178
221
|
case "RECALL_QUEUED_MESSAGE":
|
|
@@ -200,7 +243,6 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
200
243
|
onRecallQueuedMessage,
|
|
201
244
|
onClearMessages,
|
|
202
245
|
onCompact,
|
|
203
|
-
onGoalCommand,
|
|
204
246
|
]);
|
|
205
247
|
useEffect(() => {
|
|
206
248
|
onFileSelectorStateChange?.(state.showFileSelector, state.filteredFiles, state.fileSearchQuery, state.atPosition);
|
|
@@ -253,7 +295,13 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
253
295
|
useEffect(() => {
|
|
254
296
|
onImagesStateChange?.(state.attachedImages);
|
|
255
297
|
}, [state.attachedImages, onImagesStateChange]);
|
|
256
|
-
//
|
|
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]);
|
|
257
305
|
// Methods
|
|
258
306
|
const insertTextAtCursor = useCallback((text) => {
|
|
259
307
|
dispatch({ type: "INSERT_TEXT", payload: text });
|
|
@@ -397,10 +445,39 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
397
445
|
dispatch({ type: "CLEAR_LONG_TEXT_MAP" });
|
|
398
446
|
}, []);
|
|
399
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
|
+
}
|
|
400
477
|
dispatch({
|
|
401
478
|
type: "HANDLE_KEY",
|
|
402
479
|
payload: {
|
|
403
|
-
input,
|
|
480
|
+
input: result.input,
|
|
404
481
|
key,
|
|
405
482
|
hasSlashCommand: (cmd) => !!onHasSlashCommand?.(cmd),
|
|
406
483
|
hasQueuedMessages: hasQueuedMessagesProp ?? false,
|
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" });
|
|
@@ -261,6 +273,18 @@ export const handleCommandSelect = (state, dispatch, callbacks, command) => {
|
|
|
261
273
|
else if (command === "status") {
|
|
262
274
|
dispatch({ type: "SET_SHOW_STATUS_COMMAND", payload: true });
|
|
263
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
|
+
}
|
|
264
288
|
else if (command === "plugin") {
|
|
265
289
|
dispatch({ type: "SET_SHOW_PLUGIN_MANAGER", payload: true });
|
|
266
290
|
}
|
|
@@ -276,9 +300,6 @@ export const handleCommandSelect = (state, dispatch, callbacks, command) => {
|
|
|
276
300
|
else if (command === "compact") {
|
|
277
301
|
await callbacks.onCompact?.();
|
|
278
302
|
}
|
|
279
|
-
else if (command === "goal") {
|
|
280
|
-
await callbacks.onGoalCommand?.();
|
|
281
|
-
}
|
|
282
303
|
}
|
|
283
304
|
})();
|
|
284
305
|
dispatch({ type: "CANCEL_COMMAND_SELECTOR" });
|
|
@@ -507,16 +528,23 @@ export const handleNormalInput = async (state, dispatch, callbacks, input, key,
|
|
|
507
528
|
return false;
|
|
508
529
|
};
|
|
509
530
|
export const handleInput = async (state, dispatch, callbacks, input, key, clearImages) => {
|
|
510
|
-
//
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
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
|
|
520
548
|
return true;
|
|
521
549
|
}
|
|
522
550
|
if (key.escape) {
|
|
@@ -10,6 +10,15 @@ 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
|
+
};
|
|
13
22
|
export declare const ESC_DOUBLE_PRESS_TIMEOUT_MS = 1000;
|
|
14
23
|
export type PendingEffect = {
|
|
15
24
|
type: "SEND_MESSAGE";
|
|
@@ -30,6 +39,8 @@ export type PendingEffect = {
|
|
|
30
39
|
} | {
|
|
31
40
|
type: "ASK_BTW";
|
|
32
41
|
question: string;
|
|
42
|
+
} | {
|
|
43
|
+
type: "ABORT_BTW";
|
|
33
44
|
} | {
|
|
34
45
|
type: "PERMISSION_MODE_CHANGE";
|
|
35
46
|
mode: PermissionMode;
|
|
@@ -67,10 +78,9 @@ export interface InputManagerCallbacks {
|
|
|
67
78
|
onAbortMessage?: () => void;
|
|
68
79
|
onBackgroundCurrentTask?: () => void;
|
|
69
80
|
onPermissionModeChange?: (mode: PermissionMode) => void;
|
|
70
|
-
onAskBtw?: (question: string) => Promise<string>;
|
|
81
|
+
onAskBtw?: (question: string, abortSignal?: AbortSignal, onContent?: (content: string) => void) => Promise<string>;
|
|
71
82
|
onClearMessages?: () => Promise<void>;
|
|
72
83
|
onCompact?: (instructions?: string) => Promise<void>;
|
|
73
|
-
onGoalCommand?: (args?: string) => Promise<void>;
|
|
74
84
|
sessionId?: string;
|
|
75
85
|
workdir?: string;
|
|
76
86
|
getFullMessageThread?: () => Promise<{
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { getAtSelectorPosition, getSlashSelectorPosition, getWordEnd, SELECTOR_TRIGGERS, getProjectedState, } from "../utils/inputUtils.js";
|
|
2
2
|
import { AVAILABLE_COMMANDS } from "../constants/commands.js";
|
|
3
|
+
/**
|
|
4
|
+
* True while the /btw overlay is up (a question is on display, loading or
|
|
5
|
+
* answered). App's Ctrl+C exit handler checks this so Ctrl+C does not quit
|
|
6
|
+
* the app while the overlay owns the keys. Synced from useInputManager via
|
|
7
|
+
* an effect.
|
|
8
|
+
*/
|
|
9
|
+
export const btwOverlayActiveRef = { current: false };
|
|
3
10
|
export const ESC_DOUBLE_PRESS_TIMEOUT_MS = 1000;
|
|
4
11
|
export const initialState = {
|
|
5
12
|
inputText: "",
|
|
@@ -91,7 +98,7 @@ function insertTextWithPlaceholder(textToInsert, state) {
|
|
|
91
98
|
/**
|
|
92
99
|
* Submit the current input text: extract [Image #N] references, route /btw
|
|
93
100
|
* and CLI-internal slash commands, otherwise send as a message. Returns null
|
|
94
|
-
* when there is nothing to submit (empty text
|
|
101
|
+
* when there is nothing to submit (empty text).
|
|
95
102
|
*/
|
|
96
103
|
function submitInput(state) {
|
|
97
104
|
if (!state.inputText.trim()) {
|
|
@@ -112,8 +119,20 @@ function submitInput(state) {
|
|
|
112
119
|
if (contentWithPlaceholders.startsWith("/btw ")) {
|
|
113
120
|
const question = contentWithPlaceholders.substring(5).trim();
|
|
114
121
|
if (!question) {
|
|
115
|
-
//
|
|
116
|
-
return
|
|
122
|
+
// "/btw " with no question text — show usage (aligned with Claude Code)
|
|
123
|
+
return {
|
|
124
|
+
...state,
|
|
125
|
+
inputText: "",
|
|
126
|
+
cursorPosition: 0,
|
|
127
|
+
historyIndex: -1,
|
|
128
|
+
longTextMap: {},
|
|
129
|
+
attachedImages: [],
|
|
130
|
+
btwState: {
|
|
131
|
+
question: "",
|
|
132
|
+
isLoading: false,
|
|
133
|
+
answer: "Usage: /btw <your question>",
|
|
134
|
+
},
|
|
135
|
+
};
|
|
117
136
|
}
|
|
118
137
|
return {
|
|
119
138
|
...state,
|
|
@@ -131,8 +150,20 @@ function submitInput(state) {
|
|
|
131
150
|
};
|
|
132
151
|
}
|
|
133
152
|
if (contentWithPlaceholders === "/btw") {
|
|
134
|
-
// Bare /btw —
|
|
135
|
-
return
|
|
153
|
+
// Bare /btw — show usage (aligned with Claude Code)
|
|
154
|
+
return {
|
|
155
|
+
...state,
|
|
156
|
+
inputText: "",
|
|
157
|
+
cursorPosition: 0,
|
|
158
|
+
historyIndex: -1,
|
|
159
|
+
longTextMap: {},
|
|
160
|
+
attachedImages: [],
|
|
161
|
+
btwState: {
|
|
162
|
+
question: "",
|
|
163
|
+
isLoading: false,
|
|
164
|
+
answer: "Usage: /btw <your question>",
|
|
165
|
+
},
|
|
166
|
+
};
|
|
136
167
|
}
|
|
137
168
|
// Check if the content is a CLI-internal slash command (help, tasks,
|
|
138
169
|
// etc.) that should be executed locally rather than sent as a message.
|
|
@@ -593,10 +624,22 @@ export function inputReducer(state, action) {
|
|
|
593
624
|
}
|
|
594
625
|
return state;
|
|
595
626
|
}
|
|
596
|
-
// 1.
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
627
|
+
// 1. /btw overlay handling (active while a question is displayed, or
|
|
628
|
+
// the bare-/btw usage message). Only Escape dismisses (or aborts the
|
|
629
|
+
// in-flight side question while loading); every other key is ignored.
|
|
630
|
+
if (state.btwState.question || state.btwState.answer) {
|
|
631
|
+
if (key.escape) {
|
|
632
|
+
if (state.btwState.isLoading) {
|
|
633
|
+
return {
|
|
634
|
+
...state,
|
|
635
|
+
btwState: {
|
|
636
|
+
question: "",
|
|
637
|
+
answer: undefined,
|
|
638
|
+
isLoading: false,
|
|
639
|
+
},
|
|
640
|
+
pendingEffect: { type: "ABORT_BTW" },
|
|
641
|
+
};
|
|
642
|
+
}
|
|
600
643
|
return {
|
|
601
644
|
...state,
|
|
602
645
|
btwState: {
|
|
@@ -606,6 +649,11 @@ export function inputReducer(state, action) {
|
|
|
606
649
|
},
|
|
607
650
|
};
|
|
608
651
|
}
|
|
652
|
+
// Any other key while the overlay is up is ignored
|
|
653
|
+
return state;
|
|
654
|
+
}
|
|
655
|
+
// 1. Escape Handling
|
|
656
|
+
if (key.escape) {
|
|
609
657
|
if (state.showFileSelector) {
|
|
610
658
|
return {
|
|
611
659
|
...state,
|
|
@@ -21,6 +21,9 @@ export interface AgentBridgeOptions {
|
|
|
21
21
|
}
|
|
22
22
|
export declare class AgentBridge {
|
|
23
23
|
private sessions;
|
|
24
|
+
/** Pending approval requests, keyed by requestId. Stored with the resolve +
|
|
25
|
+
* context so a re-attached client can list and respond to them (daemon mode:
|
|
26
|
+
* approvals outlive any single connection). */
|
|
24
27
|
private pendingPermissions;
|
|
25
28
|
private permissionCounter;
|
|
26
29
|
private emit;
|
|
@@ -31,6 +34,21 @@ export declare class AgentBridge {
|
|
|
31
34
|
handleNotification(method: string, params: unknown): void;
|
|
32
35
|
private initialize;
|
|
33
36
|
private destroy;
|
|
37
|
+
/**
|
|
38
|
+
* True when every hosted session has settled: not generating, nothing queued,
|
|
39
|
+
* and no background work (background bash / subagents / workflows) — the same
|
|
40
|
+
* condition `wave -p` waits on before exiting (print-cli.ts). Pending
|
|
41
|
+
* permission approvals keep the owning agent's isLoading true, so they are
|
|
42
|
+
* covered without an explicit check.
|
|
43
|
+
*/
|
|
44
|
+
isIdle(): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Destroy every hosted session agent. Each Agent.destroy() saves its
|
|
47
|
+
* transcript, drains in-flight auto-memory extraction, and cleans up
|
|
48
|
+
* background tasks/subagents. Best-effort: one failing destroy must not
|
|
49
|
+
* block the rest of the shutdown.
|
|
50
|
+
*/
|
|
51
|
+
destroyAll(): Promise<void>;
|
|
34
52
|
private restoreSession;
|
|
35
53
|
private listSessions;
|
|
36
54
|
private listGitBranches;
|
|
@@ -40,6 +58,7 @@ export declare class AgentBridge {
|
|
|
40
58
|
private updateConfig;
|
|
41
59
|
private sendMessage;
|
|
42
60
|
private bang;
|
|
61
|
+
private askBtw;
|
|
43
62
|
private abortMessage;
|
|
44
63
|
private clearMessages;
|
|
45
64
|
private rewindToMessage;
|
|
@@ -64,6 +83,10 @@ export declare class AgentBridge {
|
|
|
64
83
|
private getPromptHistory;
|
|
65
84
|
private searchPromptHistory;
|
|
66
85
|
private canUseTool;
|
|
86
|
+
/** Attach snapshot: re-surface approvals that are still pending after a
|
|
87
|
+
* client disconnected (daemon mode). Responding to any listed requestId
|
|
88
|
+
* resolves the in-process promise. */
|
|
89
|
+
private listPendingPermissions;
|
|
67
90
|
private getAuthStatus;
|
|
68
91
|
private login;
|
|
69
92
|
private logout;
|