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.
Files changed (36) hide show
  1. package/dist/components/AgentsManager.d.ts +7 -0
  2. package/dist/components/AgentsManager.js +109 -0
  3. package/dist/components/ConfirmationSelector.js +17 -3
  4. package/dist/components/InputBox.js +7 -21
  5. package/dist/components/LoginCommand.js +31 -2
  6. package/dist/components/MarketplaceAddForm.js +16 -2
  7. package/dist/constants/commands.js +6 -0
  8. package/dist/contexts/useChat.d.ts +2 -1
  9. package/dist/contexts/useChat.js +15 -2
  10. package/dist/hooks/useInputManager.d.ts +2 -0
  11. package/dist/hooks/useInputManager.js +8 -0
  12. package/dist/managers/inputHandlers.js +3 -0
  13. package/dist/managers/inputReducer.d.ts +4 -0
  14. package/dist/managers/inputReducer.js +8 -0
  15. package/dist/reducers/agentsManagerReducer.d.ts +26 -0
  16. package/dist/reducers/agentsManagerReducer.js +54 -0
  17. package/dist/stdio/agentBridge.d.ts +3 -0
  18. package/dist/stdio/agentBridge.js +38 -10
  19. package/dist/stdio/protocol.d.ts +1 -1
  20. package/dist/utils/usageSummary.d.ts +0 -4
  21. package/dist/utils/usageSummary.js +1 -34
  22. package/package.json +2 -2
  23. package/src/components/AgentsManager.tsx +290 -0
  24. package/src/components/ConfirmationSelector.tsx +18 -3
  25. package/src/components/InputBox.tsx +54 -45
  26. package/src/components/LoginCommand.tsx +35 -2
  27. package/src/components/MarketplaceAddForm.tsx +17 -2
  28. package/src/constants/commands.ts +6 -0
  29. package/src/contexts/useChat.tsx +23 -2
  30. package/src/hooks/useInputManager.ts +8 -0
  31. package/src/managers/inputHandlers.ts +2 -0
  32. package/src/managers/inputReducer.ts +10 -0
  33. package/src/reducers/agentsManagerReducer.ts +91 -0
  34. package/src/stdio/agentBridge.ts +47 -9
  35. package/src/stdio/protocol.ts +2 -0
  36. 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
- setTokenInput((prev) => prev + input);
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",
@@ -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
- ? { ...b, command, exitCode, stage: "end" }
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
+ }
@@ -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
  },
@@ -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