wave-code 1.0.6 → 1.0.7
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/components/AgentsManager.d.ts +7 -0
- package/dist/components/AgentsManager.js +109 -0
- package/dist/components/ConfirmationSelector.js +17 -3
- package/dist/components/InputBox.js +7 -21
- package/dist/components/LoginCommand.js +31 -2
- package/dist/components/MarketplaceAddForm.js +16 -2
- package/dist/constants/commands.js +6 -0
- package/dist/contexts/useChat.d.ts +2 -1
- package/dist/contexts/useChat.js +15 -2
- package/dist/hooks/useInputManager.d.ts +2 -0
- package/dist/hooks/useInputManager.js +8 -0
- package/dist/managers/inputHandlers.js +3 -0
- package/dist/managers/inputReducer.d.ts +4 -0
- package/dist/managers/inputReducer.js +8 -0
- package/dist/reducers/agentsManagerReducer.d.ts +26 -0
- package/dist/reducers/agentsManagerReducer.js +54 -0
- package/dist/stdio/agentBridge.d.ts +3 -0
- package/dist/stdio/agentBridge.js +38 -10
- package/dist/stdio/protocol.d.ts +1 -1
- package/dist/utils/usageSummary.d.ts +0 -4
- package/dist/utils/usageSummary.js +1 -34
- package/package.json +2 -2
- package/src/components/AgentsManager.tsx +290 -0
- package/src/components/ConfirmationSelector.tsx +18 -3
- package/src/components/InputBox.tsx +54 -45
- package/src/components/LoginCommand.tsx +35 -2
- package/src/components/MarketplaceAddForm.tsx +17 -2
- package/src/constants/commands.ts +6 -0
- package/src/contexts/useChat.tsx +23 -2
- package/src/hooks/useInputManager.ts +8 -0
- package/src/managers/inputHandlers.ts +2 -0
- package/src/managers/inputReducer.ts +10 -0
- package/src/reducers/agentsManagerReducer.ts +91 -0
- package/src/stdio/agentBridge.ts +47 -9
- package/src/stdio/protocol.ts +2 -0
- package/src/utils/usageSummary.ts +2 -46
|
@@ -6,6 +6,7 @@ import { CommandSelector } from "./CommandSelector.js";
|
|
|
6
6
|
import { HistorySearch } from "./HistorySearch.js";
|
|
7
7
|
import { BackgroundTaskManager } from "./BackgroundTaskManager.js";
|
|
8
8
|
import { McpManager } from "./McpManager.js";
|
|
9
|
+
import { AgentsManager } from "./AgentsManager.js";
|
|
9
10
|
import { RewindCommand } from "./RewindCommand.js";
|
|
10
11
|
import { HelpView } from "./HelpView.js";
|
|
11
12
|
import { StatusCommand } from "./StatusCommand.js";
|
|
@@ -89,6 +90,7 @@ export const InputBox: React.FC<InputBoxProps> = ({
|
|
|
89
90
|
recallQueuedMessage,
|
|
90
91
|
queuedMessages,
|
|
91
92
|
setIsBtwActive,
|
|
93
|
+
agentDefinitions,
|
|
92
94
|
} = useChat();
|
|
93
95
|
|
|
94
96
|
// Ref to hold setInputText so queue callbacks can access it before useInputManager returns
|
|
@@ -133,6 +135,7 @@ export const InputBox: React.FC<InputBoxProps> = ({
|
|
|
133
135
|
// Task/MCP Manager
|
|
134
136
|
showBackgroundTaskManager,
|
|
135
137
|
showMcpManager,
|
|
138
|
+
showAgentsManager,
|
|
136
139
|
showRewindManager,
|
|
137
140
|
showHelp,
|
|
138
141
|
showStatusCommand,
|
|
@@ -142,6 +145,7 @@ export const InputBox: React.FC<InputBoxProps> = ({
|
|
|
142
145
|
showWorkflowManager,
|
|
143
146
|
setShowBackgroundTaskManager,
|
|
144
147
|
setShowMcpManager,
|
|
148
|
+
setShowAgentsManager,
|
|
145
149
|
setShowRewindManager,
|
|
146
150
|
setShowHelp,
|
|
147
151
|
setShowStatusCommand,
|
|
@@ -211,6 +215,7 @@ export const InputBox: React.FC<InputBoxProps> = ({
|
|
|
211
215
|
showModelSelector ||
|
|
212
216
|
showBackgroundTaskManager ||
|
|
213
217
|
showMcpManager ||
|
|
218
|
+
showAgentsManager ||
|
|
214
219
|
showWorkflowManager
|
|
215
220
|
) {
|
|
216
221
|
return;
|
|
@@ -254,51 +259,6 @@ export const InputBox: React.FC<InputBoxProps> = ({
|
|
|
254
259
|
await handleRewindSelect(index);
|
|
255
260
|
};
|
|
256
261
|
|
|
257
|
-
if (showRewindManager) {
|
|
258
|
-
return (
|
|
259
|
-
<RewindCommand
|
|
260
|
-
messages={messages}
|
|
261
|
-
onSelect={handleRewindSelectWithClose}
|
|
262
|
-
onCancel={handleRewindCancel}
|
|
263
|
-
getFullMessageThread={getFullMessageThread}
|
|
264
|
-
/>
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (showHelp) {
|
|
269
|
-
return (
|
|
270
|
-
<HelpView onCancel={() => setShowHelp(false)} commands={slashCommands} />
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (showStatusCommand) {
|
|
275
|
-
return <StatusCommand onCancel={() => setShowStatusCommand(false)} />;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (showLoginCommand) {
|
|
279
|
-
return <LoginCommand onCancel={() => setShowLoginCommand(false)} />;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
if (showPluginManager) {
|
|
283
|
-
return (
|
|
284
|
-
<PluginManagerShell
|
|
285
|
-
onCancel={() => setShowPluginManager(false)}
|
|
286
|
-
onPluginInstalled={recreateAgent}
|
|
287
|
-
/>
|
|
288
|
-
);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
if (showModelSelector) {
|
|
292
|
-
return (
|
|
293
|
-
<ModelSelector
|
|
294
|
-
onCancel={() => setShowModelSelector(false)}
|
|
295
|
-
currentModel={currentModel}
|
|
296
|
-
configuredModels={configuredModels}
|
|
297
|
-
onSelectModel={setModel}
|
|
298
|
-
/>
|
|
299
|
-
);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
262
|
return (
|
|
303
263
|
<Box flexDirection="column">
|
|
304
264
|
<BtwDisplay btwState={btwState} />
|
|
@@ -345,19 +305,68 @@ export const InputBox: React.FC<InputBoxProps> = ({
|
|
|
345
305
|
/>
|
|
346
306
|
)}
|
|
347
307
|
|
|
308
|
+
{showAgentsManager && (
|
|
309
|
+
<AgentsManager
|
|
310
|
+
onCancel={() => setShowAgentsManager(false)}
|
|
311
|
+
agentDefinitions={agentDefinitions}
|
|
312
|
+
/>
|
|
313
|
+
)}
|
|
314
|
+
|
|
348
315
|
{showWorkflowManager && (
|
|
349
316
|
<WorkflowManager onCancel={() => setShowWorkflowManager(false)} />
|
|
350
317
|
)}
|
|
351
318
|
|
|
319
|
+
{showRewindManager && (
|
|
320
|
+
<RewindCommand
|
|
321
|
+
messages={messages}
|
|
322
|
+
onSelect={handleRewindSelectWithClose}
|
|
323
|
+
onCancel={handleRewindCancel}
|
|
324
|
+
getFullMessageThread={getFullMessageThread}
|
|
325
|
+
/>
|
|
326
|
+
)}
|
|
327
|
+
|
|
328
|
+
{showHelp && (
|
|
329
|
+
<HelpView
|
|
330
|
+
onCancel={() => setShowHelp(false)}
|
|
331
|
+
commands={slashCommands}
|
|
332
|
+
/>
|
|
333
|
+
)}
|
|
334
|
+
|
|
335
|
+
{showStatusCommand && (
|
|
336
|
+
<StatusCommand onCancel={() => setShowStatusCommand(false)} />
|
|
337
|
+
)}
|
|
338
|
+
|
|
339
|
+
{showLoginCommand && (
|
|
340
|
+
<LoginCommand onCancel={() => setShowLoginCommand(false)} />
|
|
341
|
+
)}
|
|
342
|
+
|
|
343
|
+
{showPluginManager && (
|
|
344
|
+
<PluginManagerShell
|
|
345
|
+
onCancel={() => setShowPluginManager(false)}
|
|
346
|
+
onPluginInstalled={recreateAgent}
|
|
347
|
+
/>
|
|
348
|
+
)}
|
|
349
|
+
|
|
350
|
+
{showModelSelector && (
|
|
351
|
+
<ModelSelector
|
|
352
|
+
onCancel={() => setShowModelSelector(false)}
|
|
353
|
+
currentModel={currentModel}
|
|
354
|
+
configuredModels={configuredModels}
|
|
355
|
+
onSelectModel={setModel}
|
|
356
|
+
/>
|
|
357
|
+
)}
|
|
358
|
+
|
|
352
359
|
{btwState.question || btwState.answer
|
|
353
360
|
? null
|
|
354
361
|
: showBackgroundTaskManager ||
|
|
355
362
|
showMcpManager ||
|
|
363
|
+
showAgentsManager ||
|
|
356
364
|
showRewindManager ||
|
|
357
365
|
showHelp ||
|
|
358
366
|
showStatusCommand ||
|
|
359
367
|
showLoginCommand ||
|
|
360
368
|
showPluginManager ||
|
|
369
|
+
showModelSelector ||
|
|
361
370
|
showWorkflowManager || (
|
|
362
371
|
<Box flexDirection="column">
|
|
363
372
|
{escClearPending && <Text color="gray">再次按 Esc 清空输入</Text>}
|
|
@@ -3,6 +3,7 @@ import { Box, Text, useInput } from "ink";
|
|
|
3
3
|
import { execFile } from "child_process";
|
|
4
4
|
import { promisify } from "util";
|
|
5
5
|
import { authService } from "wave-agent-sdk";
|
|
6
|
+
import { createBracketedPasteDetector } from "../utils/bracketedPaste.js";
|
|
6
7
|
|
|
7
8
|
const execFileAsync = promisify(execFile);
|
|
8
9
|
|
|
@@ -32,6 +33,12 @@ export const LoginCommand: React.FC<LoginCommandProps> = ({ onCancel }) => {
|
|
|
32
33
|
const isLoadingRef = useRef(isLoading);
|
|
33
34
|
isLoadingRef.current = isLoading;
|
|
34
35
|
|
|
36
|
+
// Detects and strips bracketed paste markers (\x1b[200~ ... \x1b[201~)
|
|
37
|
+
// that terminals wrap pasted text in. Unlike the main InputBox pipeline,
|
|
38
|
+
// this overlay's token input goes through raw ink useInput, which only
|
|
39
|
+
// strips ONE leading ESC, leaving the markers (e.g. "[200~") in the input.
|
|
40
|
+
const pasteDetectorRef = useRef(createBracketedPasteDetector());
|
|
41
|
+
|
|
35
42
|
// Resolve/reject refs for the token promise
|
|
36
43
|
const tokenResolveRef = useRef<((token: string) => void) | null>(null);
|
|
37
44
|
const tokenRejectRef = useRef<((err: Error) => void) | null>(null);
|
|
@@ -71,9 +78,34 @@ export const LoginCommand: React.FC<LoginCommandProps> = ({ onCancel }) => {
|
|
|
71
78
|
setTokenInput((prev) => prev.slice(0, -1));
|
|
72
79
|
return;
|
|
73
80
|
}
|
|
74
|
-
// Regular character input (single or pasted multi-char)
|
|
81
|
+
// Regular character input (single or pasted multi-char). Run through the
|
|
82
|
+
// bracketed paste detector: a pasted token arrives wrapped in
|
|
83
|
+
// \x1b[200~ ... \x1b[201~ markers (possibly split across chunks), which
|
|
84
|
+
// must be stripped instead of being appended to the token.
|
|
75
85
|
if (input && !key.ctrl && !key.meta && !key.return && input.length > 0) {
|
|
76
|
-
|
|
86
|
+
const result = pasteDetectorRef.current.process(input);
|
|
87
|
+
|
|
88
|
+
if (result.kind === "consume") {
|
|
89
|
+
// In-flight bracketed paste content: hold it; the final chunk
|
|
90
|
+
// delivers the complete text.
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (result.kind === "paste") {
|
|
95
|
+
// Tokens never contain carriage returns — drop \r (CRLF terminals
|
|
96
|
+
// send \r, not \n, in pasted text).
|
|
97
|
+
const leading = result.leadingInput?.replace(/\r/g, "");
|
|
98
|
+
if (leading) {
|
|
99
|
+
setTokenInput((prev) => prev + leading);
|
|
100
|
+
}
|
|
101
|
+
const text = result.text.replace(/\r/g, "");
|
|
102
|
+
if (text) {
|
|
103
|
+
setTokenInput((prev) => prev + text);
|
|
104
|
+
}
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
setTokenInput((prev) => prev + result.input);
|
|
77
109
|
}
|
|
78
110
|
});
|
|
79
111
|
|
|
@@ -93,6 +125,7 @@ export const LoginCommand: React.FC<LoginCommandProps> = ({ onCancel }) => {
|
|
|
93
125
|
setError("");
|
|
94
126
|
setAuthUrl("");
|
|
95
127
|
setTokenInput("");
|
|
128
|
+
pasteDetectorRef.current.reset();
|
|
96
129
|
setMessage("Starting authentication...");
|
|
97
130
|
|
|
98
131
|
// Promise that resolves when user presses Enter with token input
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useReducer, useEffect } from "react";
|
|
1
|
+
import React, { useReducer, useEffect, useRef } from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
3
|
import { usePluginManagerContext } from "../contexts/PluginManagerContext.js";
|
|
4
4
|
import {
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
SCOPES,
|
|
7
7
|
type MarketplaceAddFormState,
|
|
8
8
|
} from "../reducers/marketplaceAddFormReducer.js";
|
|
9
|
+
import { createBracketedPasteDetector } from "../utils/bracketedPaste.js";
|
|
9
10
|
|
|
10
11
|
export const MarketplaceAddForm: React.FC = () => {
|
|
11
12
|
const { state: ctxState, actions } = usePluginManagerContext();
|
|
@@ -32,11 +33,25 @@ export const MarketplaceAddForm: React.FC = () => {
|
|
|
32
33
|
dispatch({ type: "CLEAR_PENDING_ACTION" });
|
|
33
34
|
}, [state.pendingAction, actions]);
|
|
34
35
|
|
|
36
|
+
const pasteDetectorRef = useRef(createBracketedPasteDetector());
|
|
37
|
+
|
|
35
38
|
useInput((input, key) => {
|
|
39
|
+
const result = pasteDetectorRef.current.process(input);
|
|
40
|
+
if (result.kind === "consume") {
|
|
41
|
+
// Content of an in-flight bracketed paste: hold it, never submit.
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
let cleanInput: string;
|
|
45
|
+
if (result.kind === "paste") {
|
|
46
|
+
cleanInput = (result.leadingInput ?? "") + result.text;
|
|
47
|
+
} else {
|
|
48
|
+
cleanInput = result.input;
|
|
49
|
+
}
|
|
50
|
+
|
|
36
51
|
dispatch({
|
|
37
52
|
type: "HANDLE_KEY",
|
|
38
53
|
key,
|
|
39
|
-
input,
|
|
54
|
+
input: cleanInput,
|
|
40
55
|
isLoading: ctxState.isLoading,
|
|
41
56
|
});
|
|
42
57
|
});
|
|
@@ -13,6 +13,12 @@ export const AVAILABLE_COMMANDS: SlashCommand[] = [
|
|
|
13
13
|
description: "View and manage MCP servers",
|
|
14
14
|
handler: () => {}, // Handler here won't be used, actual processing is in the hook
|
|
15
15
|
},
|
|
16
|
+
{
|
|
17
|
+
id: "agents",
|
|
18
|
+
name: "agents",
|
|
19
|
+
description: "List available agents and active subagents",
|
|
20
|
+
handler: () => {}, // Handler here won't be used, actual processing is in the hook
|
|
21
|
+
},
|
|
16
22
|
{
|
|
17
23
|
id: "rewind",
|
|
18
24
|
name: "rewind",
|
package/src/contexts/useChat.tsx
CHANGED
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
BackgroundTask,
|
|
16
16
|
Task,
|
|
17
17
|
SlashCommand,
|
|
18
|
+
SubagentConfiguration,
|
|
18
19
|
PermissionDecision,
|
|
19
20
|
PermissionMode,
|
|
20
21
|
QueuedMessage,
|
|
@@ -96,6 +97,8 @@ export interface ChatContextType {
|
|
|
96
97
|
// Slash Command functionality
|
|
97
98
|
slashCommands: SlashCommand[];
|
|
98
99
|
hasSlashCommand: (commandId: string) => boolean;
|
|
100
|
+
// Agent definitions (for /agents overlay)
|
|
101
|
+
agentDefinitions: SubagentConfiguration[];
|
|
99
102
|
// Permission functionality
|
|
100
103
|
permissionMode: PermissionMode;
|
|
101
104
|
setPermissionMode: (mode: PermissionMode) => void;
|
|
@@ -419,6 +422,11 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
419
422
|
// Command state
|
|
420
423
|
const [slashCommands, setSlashCommands] = useState<SlashCommand[]>([]);
|
|
421
424
|
|
|
425
|
+
// Agent definitions (for /agents overlay)
|
|
426
|
+
const [agentDefinitions, setAgentDefinitions] = useState<
|
|
427
|
+
SubagentConfiguration[]
|
|
428
|
+
>([]);
|
|
429
|
+
|
|
422
430
|
// Permission state
|
|
423
431
|
const [permissionMode, setPermissionModeState] = useState<PermissionMode>(
|
|
424
432
|
initialPermissionMode ||
|
|
@@ -627,7 +635,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
627
635
|
),
|
|
628
636
|
);
|
|
629
637
|
},
|
|
630
|
-
onCompleteBangMessage: (command, exitCode, messageId) => {
|
|
638
|
+
onCompleteBangMessage: (command, exitCode, messageId, output) => {
|
|
631
639
|
if (isExpandedRef.current) return;
|
|
632
640
|
setMessages((prev) =>
|
|
633
641
|
prev.map((m) =>
|
|
@@ -636,7 +644,13 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
636
644
|
...m,
|
|
637
645
|
blocks: m.blocks.map((b, idx) =>
|
|
638
646
|
idx === m.blocks.length - 1 && b.type === "bang"
|
|
639
|
-
? {
|
|
647
|
+
? {
|
|
648
|
+
...b,
|
|
649
|
+
command,
|
|
650
|
+
exitCode,
|
|
651
|
+
stage: "end",
|
|
652
|
+
...(output !== undefined ? { output } : {}),
|
|
653
|
+
}
|
|
640
654
|
: b,
|
|
641
655
|
),
|
|
642
656
|
}
|
|
@@ -778,6 +792,11 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
778
792
|
// Get initial commands
|
|
779
793
|
const agentSlashCommands = agent.getSlashCommands?.() || [];
|
|
780
794
|
setSlashCommands(agentSlashCommands);
|
|
795
|
+
|
|
796
|
+
// Get initial agent definitions
|
|
797
|
+
const initialAgentDefinitions =
|
|
798
|
+
agent.getSubagentConfigurations?.() || [];
|
|
799
|
+
setAgentDefinitions(initialAgentDefinitions);
|
|
781
800
|
} catch (error) {
|
|
782
801
|
console.error("Failed to initialize AI manager:", error);
|
|
783
802
|
}
|
|
@@ -818,6 +837,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
818
837
|
setMessages([]);
|
|
819
838
|
setMcpServerStatuses([]);
|
|
820
839
|
setSlashCommands([]);
|
|
840
|
+
setAgentDefinitions([]);
|
|
821
841
|
setSessionId("");
|
|
822
842
|
setIsLoading(false);
|
|
823
843
|
setLatestTotalTokens(0);
|
|
@@ -1205,6 +1225,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
1205
1225
|
stopBackgroundTask,
|
|
1206
1226
|
slashCommands,
|
|
1207
1227
|
hasSlashCommand,
|
|
1228
|
+
agentDefinitions,
|
|
1208
1229
|
permissionMode,
|
|
1209
1230
|
setPermissionMode,
|
|
1210
1231
|
isConfirmationVisible,
|
|
@@ -242,6 +242,8 @@ export const useInputManager = (
|
|
|
242
242
|
});
|
|
243
243
|
} else if (command === "mcp") {
|
|
244
244
|
dispatch({ type: "SET_SHOW_MCP_MANAGER", payload: true });
|
|
245
|
+
} else if (command === "agents") {
|
|
246
|
+
dispatch({ type: "SET_SHOW_AGENTS_MANAGER", payload: true });
|
|
245
247
|
} else if (command === "rewind") {
|
|
246
248
|
dispatch({ type: "SET_SHOW_REWIND_MANAGER", payload: true });
|
|
247
249
|
} else if (command === "help") {
|
|
@@ -493,6 +495,10 @@ export const useInputManager = (
|
|
|
493
495
|
dispatch({ type: "SET_SHOW_MCP_MANAGER", payload: show });
|
|
494
496
|
}, []);
|
|
495
497
|
|
|
498
|
+
const setShowAgentsManager = useCallback((show: boolean) => {
|
|
499
|
+
dispatch({ type: "SET_SHOW_AGENTS_MANAGER", payload: show });
|
|
500
|
+
}, []);
|
|
501
|
+
|
|
496
502
|
const setShowRewindManager = useCallback((show: boolean) => {
|
|
497
503
|
dispatch({ type: "SET_SHOW_REWIND_MANAGER", payload: show });
|
|
498
504
|
}, []);
|
|
@@ -657,6 +663,7 @@ export const useInputManager = (
|
|
|
657
663
|
historySearchQuery: state.historySearchQuery,
|
|
658
664
|
showBackgroundTaskManager: state.showBackgroundTaskManager,
|
|
659
665
|
showMcpManager: state.showMcpManager,
|
|
666
|
+
showAgentsManager: state.showAgentsManager,
|
|
660
667
|
showRewindManager: state.showRewindManager,
|
|
661
668
|
showHelp: state.showHelp,
|
|
662
669
|
showStatusCommand: state.showStatusCommand,
|
|
@@ -702,6 +709,7 @@ export const useInputManager = (
|
|
|
702
709
|
// Bash/MCP Manager
|
|
703
710
|
setShowBackgroundTaskManager,
|
|
704
711
|
setShowMcpManager,
|
|
712
|
+
setShowAgentsManager,
|
|
705
713
|
setShowRewindManager,
|
|
706
714
|
setShowHelp,
|
|
707
715
|
setShowStatusCommand,
|
|
@@ -367,6 +367,8 @@ export const handleCommandSelect = (
|
|
|
367
367
|
});
|
|
368
368
|
} else if (command === "mcp") {
|
|
369
369
|
dispatch({ type: "SET_SHOW_MCP_MANAGER", payload: true });
|
|
370
|
+
} else if (command === "agents") {
|
|
371
|
+
dispatch({ type: "SET_SHOW_AGENTS_MANAGER", payload: true });
|
|
370
372
|
} else if (command === "rewind") {
|
|
371
373
|
dispatch({ type: "SET_SHOW_REWIND_MANAGER", payload: true });
|
|
372
374
|
} else if (command === "help") {
|
|
@@ -130,6 +130,7 @@ export interface InputState {
|
|
|
130
130
|
imageIdCounter: number;
|
|
131
131
|
showBackgroundTaskManager: boolean;
|
|
132
132
|
showMcpManager: boolean;
|
|
133
|
+
showAgentsManager: boolean;
|
|
133
134
|
showRewindManager: boolean;
|
|
134
135
|
showHelp: boolean;
|
|
135
136
|
showStatusCommand: boolean;
|
|
@@ -167,6 +168,7 @@ export const initialState: InputState = {
|
|
|
167
168
|
imageIdCounter: 1,
|
|
168
169
|
showBackgroundTaskManager: false,
|
|
169
170
|
showMcpManager: false,
|
|
171
|
+
showAgentsManager: false,
|
|
170
172
|
showRewindManager: false,
|
|
171
173
|
showHelp: false,
|
|
172
174
|
showStatusCommand: false,
|
|
@@ -400,6 +402,7 @@ export type InputAction =
|
|
|
400
402
|
| { type: "CLEAR_IMAGES" }
|
|
401
403
|
| { type: "SET_SHOW_BACKGROUND_TASK_MANAGER"; payload: boolean }
|
|
402
404
|
| { type: "SET_SHOW_MCP_MANAGER"; payload: boolean }
|
|
405
|
+
| { type: "SET_SHOW_AGENTS_MANAGER"; payload: boolean }
|
|
403
406
|
| { type: "SET_SHOW_REWIND_MANAGER"; payload: boolean }
|
|
404
407
|
| { type: "SET_SHOW_HELP"; payload: boolean }
|
|
405
408
|
| { type: "SET_SHOW_STATUS_COMMAND"; payload: boolean }
|
|
@@ -589,6 +592,12 @@ export function inputReducer(
|
|
|
589
592
|
showMcpManager: action.payload,
|
|
590
593
|
selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
|
|
591
594
|
};
|
|
595
|
+
case "SET_SHOW_AGENTS_MANAGER":
|
|
596
|
+
return {
|
|
597
|
+
...state,
|
|
598
|
+
showAgentsManager: action.payload,
|
|
599
|
+
selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
|
|
600
|
+
};
|
|
592
601
|
case "SET_SHOW_REWIND_MANAGER":
|
|
593
602
|
return {
|
|
594
603
|
...state,
|
|
@@ -970,6 +979,7 @@ export function inputReducer(
|
|
|
970
979
|
!(
|
|
971
980
|
state.showBackgroundTaskManager ||
|
|
972
981
|
state.showMcpManager ||
|
|
982
|
+
state.showAgentsManager ||
|
|
973
983
|
state.showRewindManager ||
|
|
974
984
|
state.showHelp ||
|
|
975
985
|
state.showStatusCommand ||
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Key } from "ink";
|
|
2
|
+
|
|
3
|
+
export type PendingEffect = { type: "CANCEL" };
|
|
4
|
+
|
|
5
|
+
export interface AgentsManagerState {
|
|
6
|
+
selectedIndex: number;
|
|
7
|
+
viewMode: "list" | "detail";
|
|
8
|
+
pendingEffect: PendingEffect | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type AgentsManagerAction =
|
|
12
|
+
| { type: "MOVE_UP" }
|
|
13
|
+
| { type: "MOVE_DOWN"; itemCount: number }
|
|
14
|
+
| { type: "SET_VIEW_MODE"; viewMode: "list" | "detail" }
|
|
15
|
+
| {
|
|
16
|
+
type: "HANDLE_KEY";
|
|
17
|
+
input: string;
|
|
18
|
+
key: Key;
|
|
19
|
+
itemCount: number;
|
|
20
|
+
}
|
|
21
|
+
| { type: "CLEAR_PENDING_EFFECT" };
|
|
22
|
+
|
|
23
|
+
export function agentsManagerReducer(
|
|
24
|
+
state: AgentsManagerState,
|
|
25
|
+
action: AgentsManagerAction,
|
|
26
|
+
): AgentsManagerState {
|
|
27
|
+
switch (action.type) {
|
|
28
|
+
case "MOVE_UP":
|
|
29
|
+
return {
|
|
30
|
+
...state,
|
|
31
|
+
selectedIndex: Math.max(0, state.selectedIndex - 1),
|
|
32
|
+
};
|
|
33
|
+
case "MOVE_DOWN":
|
|
34
|
+
return {
|
|
35
|
+
...state,
|
|
36
|
+
selectedIndex: Math.min(
|
|
37
|
+
Math.max(0, action.itemCount - 1),
|
|
38
|
+
state.selectedIndex + 1,
|
|
39
|
+
),
|
|
40
|
+
};
|
|
41
|
+
case "SET_VIEW_MODE":
|
|
42
|
+
return { ...state, viewMode: action.viewMode };
|
|
43
|
+
case "HANDLE_KEY": {
|
|
44
|
+
const { key, itemCount } = action;
|
|
45
|
+
|
|
46
|
+
if (key.return) {
|
|
47
|
+
if (state.viewMode === "list") {
|
|
48
|
+
return { ...state, viewMode: "detail" };
|
|
49
|
+
}
|
|
50
|
+
// Aligned with Claude Code AgentDetail: Enter returns to the list.
|
|
51
|
+
return { ...state, viewMode: "list" };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (key.escape) {
|
|
55
|
+
if (state.viewMode === "detail") {
|
|
56
|
+
return { ...state, viewMode: "list" };
|
|
57
|
+
}
|
|
58
|
+
return { ...state, pendingEffect: { type: "CANCEL" } };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Detail view does not respond to arrow keys (aligned with CC
|
|
62
|
+
// AgentDetail, which only Esc/Enter back to the list).
|
|
63
|
+
if (state.viewMode === "detail") {
|
|
64
|
+
return state;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (key.upArrow) {
|
|
68
|
+
return {
|
|
69
|
+
...state,
|
|
70
|
+
selectedIndex: Math.max(0, state.selectedIndex - 1),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (key.downArrow) {
|
|
75
|
+
return {
|
|
76
|
+
...state,
|
|
77
|
+
selectedIndex: Math.min(
|
|
78
|
+
Math.max(0, itemCount - 1),
|
|
79
|
+
state.selectedIndex + 1,
|
|
80
|
+
),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return state;
|
|
85
|
+
}
|
|
86
|
+
case "CLEAR_PENDING_EFFECT":
|
|
87
|
+
return { ...state, pendingEffect: null };
|
|
88
|
+
default:
|
|
89
|
+
return state;
|
|
90
|
+
}
|
|
91
|
+
}
|
package/src/stdio/agentBridge.ts
CHANGED
|
@@ -42,6 +42,8 @@ import {
|
|
|
42
42
|
PluginCore,
|
|
43
43
|
validateWorktreeRemovalPath,
|
|
44
44
|
type SlashCommand,
|
|
45
|
+
loadUserConfigEnv,
|
|
46
|
+
type SubagentConfiguration,
|
|
45
47
|
} from "wave-agent-sdk";
|
|
46
48
|
import {
|
|
47
49
|
type JsonRpcError,
|
|
@@ -140,6 +142,15 @@ export class AgentBridge {
|
|
|
140
142
|
|
|
141
143
|
constructor(options: AgentBridgeOptions) {
|
|
142
144
|
this.emit = options.emit;
|
|
145
|
+
// Mirror the user-level settings env WAVE_SERVER_URL into process.env
|
|
146
|
+
// before any agent initializes. getAuthStatus (webviewReady →
|
|
147
|
+
// pushInitialState) can run before the first agent, and AuthService falls
|
|
148
|
+
// back to the default URL otherwise — refreshing a custom-domain token
|
|
149
|
+
// against the wrong host 401s into a logged-out state.
|
|
150
|
+
const userEnv = loadUserConfigEnv();
|
|
151
|
+
if (userEnv.WAVE_SERVER_URL) {
|
|
152
|
+
process.env.WAVE_SERVER_URL = userEnv.WAVE_SERVER_URL;
|
|
153
|
+
}
|
|
143
154
|
}
|
|
144
155
|
|
|
145
156
|
// ── Public API ────────────────────────────────────────────────
|
|
@@ -166,6 +177,10 @@ export class AgentBridge {
|
|
|
166
177
|
return this.listPendingPermissions();
|
|
167
178
|
case "updateConfig":
|
|
168
179
|
return this.updateConfig(p as unknown as UpdateConfigParams, sessionId);
|
|
180
|
+
case "getConfiguredModels":
|
|
181
|
+
return this.getConfiguredModels(sessionId);
|
|
182
|
+
case "setModel":
|
|
183
|
+
return this.setModel(p.model as string, sessionId);
|
|
169
184
|
|
|
170
185
|
// ── Messages ──
|
|
171
186
|
case "sendMessage":
|
|
@@ -222,6 +237,8 @@ export class AgentBridge {
|
|
|
222
237
|
// ── Commands ──
|
|
223
238
|
case "getSlashCommands":
|
|
224
239
|
return this.getSlashCommands(sessionId);
|
|
240
|
+
case "getSubagentConfigurations":
|
|
241
|
+
return this.getSubagentConfigurations(sessionId);
|
|
225
242
|
|
|
226
243
|
// ── File / History (global — no session required) ──
|
|
227
244
|
case "searchFiles":
|
|
@@ -727,6 +744,27 @@ export class AgentBridge {
|
|
|
727
744
|
return { sessionId: agent.sessionId };
|
|
728
745
|
}
|
|
729
746
|
|
|
747
|
+
private getConfiguredModels(sessionId?: string): {
|
|
748
|
+
models: string[];
|
|
749
|
+
currentModel: string | undefined;
|
|
750
|
+
} {
|
|
751
|
+
const entry = this.requireSession(sessionId);
|
|
752
|
+
return {
|
|
753
|
+
models: entry.agent.getConfiguredModels(),
|
|
754
|
+
currentModel: entry.agent.getModelConfig().model,
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
private async setModel(model: string, sessionId?: string): Promise<null> {
|
|
759
|
+
const entry = this.requireSession(sessionId);
|
|
760
|
+
entry.agent.setModel(model);
|
|
761
|
+
// Keep storedConfig in sync: updateConfig recreates the agent from
|
|
762
|
+
// storedConfig, so without this a later config save would revert the
|
|
763
|
+
// model chosen here.
|
|
764
|
+
entry.storedConfig = { ...entry.storedConfig, model };
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
|
|
730
768
|
// ── Messages ──────────────────────────────────────────────────
|
|
731
769
|
|
|
732
770
|
private async sendMessage(
|
|
@@ -984,6 +1022,13 @@ export class AgentBridge {
|
|
|
984
1022
|
return { commands: entry.agent.getSlashCommands() };
|
|
985
1023
|
}
|
|
986
1024
|
|
|
1025
|
+
private getSubagentConfigurations(sessionId?: string): {
|
|
1026
|
+
configurations: SubagentConfiguration[];
|
|
1027
|
+
} {
|
|
1028
|
+
const entry = this.requireSession(sessionId);
|
|
1029
|
+
return { configurations: entry.agent.getSubagentConfigurations() };
|
|
1030
|
+
}
|
|
1031
|
+
|
|
987
1032
|
// ── File / History (global) ───────────────────────────────────
|
|
988
1033
|
|
|
989
1034
|
private async searchFiles(
|
|
@@ -1107,13 +1152,6 @@ export class AgentBridge {
|
|
|
1107
1152
|
serverUrl: string;
|
|
1108
1153
|
}> {
|
|
1109
1154
|
const authService = AuthService.getInstance();
|
|
1110
|
-
// A stale-but-refreshable token still means "logged in" — the daemon may
|
|
1111
|
-
// have started with an expired access token (hourly expiry) and only
|
|
1112
|
-
// refreshes lazily on the first API call. Without this proactive refresh a
|
|
1113
|
-
// fresh client querying right after daemon start gets a false
|
|
1114
|
-
// isAuthenticated and e.g. the desktop welcome page keeps showing the
|
|
1115
|
-
// login button for an authenticated host. Mirrors the refresh that
|
|
1116
|
-
// createAuthAwareFetch does before every real request.
|
|
1117
1155
|
await authService.checkAndRefreshTokenIfNeeded();
|
|
1118
1156
|
return {
|
|
1119
1157
|
isAuthenticated: authService.isSSOAuthenticated(),
|
|
@@ -1424,10 +1462,10 @@ export class AgentBridge {
|
|
|
1424
1462
|
ctx.registeredSessionId,
|
|
1425
1463
|
);
|
|
1426
1464
|
},
|
|
1427
|
-
onCompleteBangMessage: (command, exitCode, messageId) => {
|
|
1465
|
+
onCompleteBangMessage: (command, exitCode, messageId, output) => {
|
|
1428
1466
|
this.emit(
|
|
1429
1467
|
"bangMessageCompleted",
|
|
1430
|
-
{ command, exitCode, messageId },
|
|
1468
|
+
{ command, exitCode, messageId, output },
|
|
1431
1469
|
ctx.registeredSessionId,
|
|
1432
1470
|
);
|
|
1433
1471
|
},
|
package/src/stdio/protocol.ts
CHANGED
|
@@ -75,6 +75,8 @@ export type RequestMethod =
|
|
|
75
75
|
| "getPromptHistory"
|
|
76
76
|
| "searchPromptHistory"
|
|
77
77
|
| "updateConfig"
|
|
78
|
+
| "getConfiguredModels"
|
|
79
|
+
| "setModel"
|
|
78
80
|
// Permissions (daemon attach: re-surface pending approvals after reconnect)
|
|
79
81
|
| "listPendingPermissions"
|
|
80
82
|
// Auth
|