wave-code 1.0.5 → 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 +96 -21
- 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 +4 -0
- package/dist/stdio/agentBridge.js +45 -10
- package/dist/stdio/protocol.d.ts +1 -1
- package/dist/utils/rewindCheckpoints.d.ts +2 -2
- package/dist/utils/rewindCheckpoints.js +4 -2
- 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 +159 -72
- 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 +55 -9
- package/src/stdio/protocol.ts +2 -0
- package/src/utils/rewindCheckpoints.ts +3 -2
- package/src/utils/usageSummary.ts +2 -46
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;
|
|
@@ -158,6 +161,84 @@ export interface ChatProviderProps extends BaseAppProps {
|
|
|
158
161
|
children: React.ReactNode;
|
|
159
162
|
}
|
|
160
163
|
|
|
164
|
+
interface StreamingUpdateParams {
|
|
165
|
+
messageId: string;
|
|
166
|
+
chunk: string;
|
|
167
|
+
stage: "streaming" | "end";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Window-concat throttle for pure-delta streaming updates: chunks arriving
|
|
172
|
+
* within the cooldown window are merged so no delta is lost (a dropped delta
|
|
173
|
+
* would permanently lose content, unlike the accumulated-payload throttle it
|
|
174
|
+
* replaces). Leading edge fires immediately; the trailing edge carries only
|
|
175
|
+
* chunks that arrived within the window. `end` flushes any pending deltas
|
|
176
|
+
* first, then forwards the end signal right away.
|
|
177
|
+
*/
|
|
178
|
+
function createStreamingWindowThrottle(
|
|
179
|
+
fn: (params: StreamingUpdateParams) => void,
|
|
180
|
+
wait: number,
|
|
181
|
+
): {
|
|
182
|
+
(params: StreamingUpdateParams): void;
|
|
183
|
+
cancel: () => void;
|
|
184
|
+
flush: () => void;
|
|
185
|
+
} {
|
|
186
|
+
let timer: NodeJS.Timeout | null = null;
|
|
187
|
+
let pending: { messageId: string; chunk: string } | null = null;
|
|
188
|
+
|
|
189
|
+
const fire = (stage: "streaming" | "end") => {
|
|
190
|
+
if (pending) {
|
|
191
|
+
fn({ ...pending, stage });
|
|
192
|
+
pending = null;
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const throttled = (params: StreamingUpdateParams) => {
|
|
197
|
+
if (params.stage === "end") {
|
|
198
|
+
// Flush any deltas still pending inside the cooldown window first
|
|
199
|
+
if (timer) {
|
|
200
|
+
clearTimeout(timer);
|
|
201
|
+
timer = null;
|
|
202
|
+
}
|
|
203
|
+
fire("streaming");
|
|
204
|
+
fn(params);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (pending) {
|
|
208
|
+
pending.chunk += params.chunk;
|
|
209
|
+
} else {
|
|
210
|
+
pending = { messageId: params.messageId, chunk: params.chunk };
|
|
211
|
+
}
|
|
212
|
+
if (!timer) {
|
|
213
|
+
// Leading edge: fire the current delta immediately, then reset pending so
|
|
214
|
+
// the trailing edge only carries chunks arriving within this window
|
|
215
|
+
fire("streaming");
|
|
216
|
+
timer = setTimeout(() => {
|
|
217
|
+
timer = null;
|
|
218
|
+
fire("streaming");
|
|
219
|
+
}, wait);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
throttled.cancel = () => {
|
|
224
|
+
if (timer) {
|
|
225
|
+
clearTimeout(timer);
|
|
226
|
+
timer = null;
|
|
227
|
+
}
|
|
228
|
+
pending = null;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
throttled.flush = () => {
|
|
232
|
+
if (timer) {
|
|
233
|
+
clearTimeout(timer);
|
|
234
|
+
timer = null;
|
|
235
|
+
}
|
|
236
|
+
fire("streaming");
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
return throttled;
|
|
240
|
+
}
|
|
241
|
+
|
|
161
242
|
export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
162
243
|
children,
|
|
163
244
|
bypassPermissions,
|
|
@@ -188,86 +269,77 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
188
269
|
const [latestTotalTokens, setLatestTotalTokens] = useState(0);
|
|
189
270
|
const [maxInputTokens, setMaxInputTokens] = useState(200000);
|
|
190
271
|
|
|
191
|
-
// Throttled incremental streaming updaters — 500ms
|
|
192
|
-
// as the pre-incremental throttledSetMessages.
|
|
193
|
-
//
|
|
272
|
+
// Throttled incremental streaming updaters — 500ms window-concat, the same
|
|
273
|
+
// interval as the pre-incremental throttledSetMessages. Chunks are pure
|
|
274
|
+
// deltas: within-window chunks are merged so none is dropped, and
|
|
275
|
+
// `stage === "end"` flushes pending deltas + applies the end signal
|
|
276
|
+
// immediately so completion results are never delayed.
|
|
194
277
|
const throttledContentUpdate = useMemo(
|
|
195
278
|
() =>
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
setMessages((prev) =>
|
|
204
|
-
prev.map((m) => {
|
|
205
|
-
if (m.id !== messageId) return m;
|
|
206
|
-
const textBlockIndex = m.blocks.findIndex(
|
|
207
|
-
(b) => b.type === "text",
|
|
208
|
-
);
|
|
209
|
-
if (textBlockIndex === -1) {
|
|
210
|
-
return {
|
|
211
|
-
...m,
|
|
212
|
-
blocks: [
|
|
213
|
-
...m.blocks,
|
|
214
|
-
{ type: "text", content: accumulated, stage },
|
|
215
|
-
],
|
|
216
|
-
};
|
|
217
|
-
}
|
|
279
|
+
createStreamingWindowThrottle((params) => {
|
|
280
|
+
const { messageId, chunk, stage } = params;
|
|
281
|
+
setMessages((prev) =>
|
|
282
|
+
prev.map((m) => {
|
|
283
|
+
if (m.id !== messageId) return m;
|
|
284
|
+
const textBlockIndex = m.blocks.findIndex((b) => b.type === "text");
|
|
285
|
+
if (textBlockIndex === -1) {
|
|
218
286
|
return {
|
|
219
287
|
...m,
|
|
220
|
-
blocks: m.blocks
|
|
221
|
-
idx === textBlockIndex && b.type === "text"
|
|
222
|
-
? { ...b, content: accumulated, stage }
|
|
223
|
-
: b,
|
|
224
|
-
),
|
|
288
|
+
blocks: [...m.blocks, { type: "text", content: chunk, stage }],
|
|
225
289
|
};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
...m,
|
|
293
|
+
blocks: m.blocks.map((b, idx) =>
|
|
294
|
+
idx === textBlockIndex && b.type === "text"
|
|
295
|
+
? {
|
|
296
|
+
...b,
|
|
297
|
+
content: (b.content || "") + chunk,
|
|
298
|
+
stage,
|
|
299
|
+
}
|
|
300
|
+
: b,
|
|
301
|
+
),
|
|
302
|
+
};
|
|
303
|
+
}),
|
|
304
|
+
);
|
|
305
|
+
}, 500),
|
|
231
306
|
[],
|
|
232
307
|
);
|
|
233
308
|
|
|
234
309
|
const throttledReasoningUpdate = useMemo(
|
|
235
310
|
() =>
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
if (m.id !== messageId) return m;
|
|
246
|
-
const reasoningBlockIndex = m.blocks.findIndex(
|
|
247
|
-
(b) => b.type === "reasoning",
|
|
248
|
-
);
|
|
249
|
-
if (reasoningBlockIndex === -1) {
|
|
250
|
-
return {
|
|
251
|
-
...m,
|
|
252
|
-
blocks: [
|
|
253
|
-
...m.blocks,
|
|
254
|
-
{ type: "reasoning", content: accumulated, stage },
|
|
255
|
-
],
|
|
256
|
-
};
|
|
257
|
-
}
|
|
311
|
+
createStreamingWindowThrottle((params) => {
|
|
312
|
+
const { messageId, chunk, stage } = params;
|
|
313
|
+
setMessages((prev) =>
|
|
314
|
+
prev.map((m) => {
|
|
315
|
+
if (m.id !== messageId) return m;
|
|
316
|
+
const reasoningBlockIndex = m.blocks.findIndex(
|
|
317
|
+
(b) => b.type === "reasoning",
|
|
318
|
+
);
|
|
319
|
+
if (reasoningBlockIndex === -1) {
|
|
258
320
|
return {
|
|
259
321
|
...m,
|
|
260
|
-
blocks:
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
),
|
|
322
|
+
blocks: [
|
|
323
|
+
...m.blocks,
|
|
324
|
+
{ type: "reasoning", content: chunk, stage },
|
|
325
|
+
],
|
|
265
326
|
};
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
...m,
|
|
330
|
+
blocks: m.blocks.map((b, idx) =>
|
|
331
|
+
idx === reasoningBlockIndex && b.type === "reasoning"
|
|
332
|
+
? {
|
|
333
|
+
...b,
|
|
334
|
+
content: (b.content || "") + chunk,
|
|
335
|
+
stage,
|
|
336
|
+
}
|
|
337
|
+
: b,
|
|
338
|
+
),
|
|
339
|
+
};
|
|
340
|
+
}),
|
|
341
|
+
);
|
|
342
|
+
}, 500),
|
|
271
343
|
[],
|
|
272
344
|
);
|
|
273
345
|
|
|
@@ -350,6 +422,11 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
350
422
|
// Command state
|
|
351
423
|
const [slashCommands, setSlashCommands] = useState<SlashCommand[]>([]);
|
|
352
424
|
|
|
425
|
+
// Agent definitions (for /agents overlay)
|
|
426
|
+
const [agentDefinitions, setAgentDefinitions] = useState<
|
|
427
|
+
SubagentConfiguration[]
|
|
428
|
+
>([]);
|
|
429
|
+
|
|
353
430
|
// Permission state
|
|
354
431
|
const [permissionMode, setPermissionModeState] = useState<PermissionMode>(
|
|
355
432
|
initialPermissionMode ||
|
|
@@ -477,12 +554,10 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
477
554
|
onAssistantContentUpdated: (params) => {
|
|
478
555
|
if (isExpandedRef.current) return;
|
|
479
556
|
throttledContentUpdate(params);
|
|
480
|
-
if (params.stage === "end") throttledContentUpdate.flush();
|
|
481
557
|
},
|
|
482
558
|
onAssistantReasoningUpdated: (params) => {
|
|
483
559
|
if (isExpandedRef.current) return;
|
|
484
560
|
throttledReasoningUpdate(params);
|
|
485
|
-
if (params.stage === "end") throttledReasoningUpdate.flush();
|
|
486
561
|
},
|
|
487
562
|
onToolBlockUpdated: (params) => {
|
|
488
563
|
if (isExpandedRef.current) return;
|
|
@@ -560,7 +635,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
560
635
|
),
|
|
561
636
|
);
|
|
562
637
|
},
|
|
563
|
-
onCompleteBangMessage: (command, exitCode, messageId) => {
|
|
638
|
+
onCompleteBangMessage: (command, exitCode, messageId, output) => {
|
|
564
639
|
if (isExpandedRef.current) return;
|
|
565
640
|
setMessages((prev) =>
|
|
566
641
|
prev.map((m) =>
|
|
@@ -569,7 +644,13 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
569
644
|
...m,
|
|
570
645
|
blocks: m.blocks.map((b, idx) =>
|
|
571
646
|
idx === m.blocks.length - 1 && b.type === "bang"
|
|
572
|
-
? {
|
|
647
|
+
? {
|
|
648
|
+
...b,
|
|
649
|
+
command,
|
|
650
|
+
exitCode,
|
|
651
|
+
stage: "end",
|
|
652
|
+
...(output !== undefined ? { output } : {}),
|
|
653
|
+
}
|
|
573
654
|
: b,
|
|
574
655
|
),
|
|
575
656
|
}
|
|
@@ -711,6 +792,11 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
711
792
|
// Get initial commands
|
|
712
793
|
const agentSlashCommands = agent.getSlashCommands?.() || [];
|
|
713
794
|
setSlashCommands(agentSlashCommands);
|
|
795
|
+
|
|
796
|
+
// Get initial agent definitions
|
|
797
|
+
const initialAgentDefinitions =
|
|
798
|
+
agent.getSubagentConfigurations?.() || [];
|
|
799
|
+
setAgentDefinitions(initialAgentDefinitions);
|
|
714
800
|
} catch (error) {
|
|
715
801
|
console.error("Failed to initialize AI manager:", error);
|
|
716
802
|
}
|
|
@@ -730,7 +816,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
730
816
|
originalCwd,
|
|
731
817
|
model,
|
|
732
818
|
initialPermissionMode,
|
|
733
|
-
refreshMessages,
|
|
734
819
|
throttledContentUpdate,
|
|
735
820
|
throttledReasoningUpdate,
|
|
736
821
|
throttledToolBlockUpdate,
|
|
@@ -752,6 +837,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
752
837
|
setMessages([]);
|
|
753
838
|
setMcpServerStatuses([]);
|
|
754
839
|
setSlashCommands([]);
|
|
840
|
+
setAgentDefinitions([]);
|
|
755
841
|
setSessionId("");
|
|
756
842
|
setIsLoading(false);
|
|
757
843
|
setLatestTotalTokens(0);
|
|
@@ -1139,6 +1225,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
1139
1225
|
stopBackgroundTask,
|
|
1140
1226
|
slashCommands,
|
|
1141
1227
|
hasSlashCommand,
|
|
1228
|
+
agentDefinitions,
|
|
1142
1229
|
permissionMode,
|
|
1143
1230
|
setPermissionMode,
|
|
1144
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":
|
|
@@ -335,6 +352,8 @@ export class AgentBridge {
|
|
|
335
352
|
return this.getBackgroundTaskOutput(p.taskId as string, sessionId);
|
|
336
353
|
case "stopBackgroundTask":
|
|
337
354
|
return this.stopBackgroundTask(p.taskId as string, sessionId);
|
|
355
|
+
case "backgroundCurrentTask":
|
|
356
|
+
return this.backgroundCurrentTask(sessionId);
|
|
338
357
|
|
|
339
358
|
case "getWorkflowRuns":
|
|
340
359
|
return this.getWorkflowRuns(sessionId);
|
|
@@ -725,6 +744,27 @@ export class AgentBridge {
|
|
|
725
744
|
return { sessionId: agent.sessionId };
|
|
726
745
|
}
|
|
727
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
|
+
|
|
728
768
|
// ── Messages ──────────────────────────────────────────────────
|
|
729
769
|
|
|
730
770
|
private async sendMessage(
|
|
@@ -905,6 +945,12 @@ export class AgentBridge {
|
|
|
905
945
|
return { success };
|
|
906
946
|
}
|
|
907
947
|
|
|
948
|
+
private async backgroundCurrentTask(sessionId?: string): Promise<null> {
|
|
949
|
+
const entry = this.requireSession(sessionId);
|
|
950
|
+
await entry.agent.backgroundCurrentTask();
|
|
951
|
+
return null;
|
|
952
|
+
}
|
|
953
|
+
|
|
908
954
|
private async getWorkflowRuns(
|
|
909
955
|
sessionId?: string,
|
|
910
956
|
): Promise<{ runs: SerializableWorkflowRun[] }> {
|
|
@@ -976,6 +1022,13 @@ export class AgentBridge {
|
|
|
976
1022
|
return { commands: entry.agent.getSlashCommands() };
|
|
977
1023
|
}
|
|
978
1024
|
|
|
1025
|
+
private getSubagentConfigurations(sessionId?: string): {
|
|
1026
|
+
configurations: SubagentConfiguration[];
|
|
1027
|
+
} {
|
|
1028
|
+
const entry = this.requireSession(sessionId);
|
|
1029
|
+
return { configurations: entry.agent.getSubagentConfigurations() };
|
|
1030
|
+
}
|
|
1031
|
+
|
|
979
1032
|
// ── File / History (global) ───────────────────────────────────
|
|
980
1033
|
|
|
981
1034
|
private async searchFiles(
|
|
@@ -1099,13 +1152,6 @@ export class AgentBridge {
|
|
|
1099
1152
|
serverUrl: string;
|
|
1100
1153
|
}> {
|
|
1101
1154
|
const authService = AuthService.getInstance();
|
|
1102
|
-
// A stale-but-refreshable token still means "logged in" — the daemon may
|
|
1103
|
-
// have started with an expired access token (hourly expiry) and only
|
|
1104
|
-
// refreshes lazily on the first API call. Without this proactive refresh a
|
|
1105
|
-
// fresh client querying right after daemon start gets a false
|
|
1106
|
-
// isAuthenticated and e.g. the desktop welcome page keeps showing the
|
|
1107
|
-
// login button for an authenticated host. Mirrors the refresh that
|
|
1108
|
-
// createAuthAwareFetch does before every real request.
|
|
1109
1155
|
await authService.checkAndRefreshTokenIfNeeded();
|
|
1110
1156
|
return {
|
|
1111
1157
|
isAuthenticated: authService.isSSOAuthenticated(),
|
|
@@ -1416,10 +1462,10 @@ export class AgentBridge {
|
|
|
1416
1462
|
ctx.registeredSessionId,
|
|
1417
1463
|
);
|
|
1418
1464
|
},
|
|
1419
|
-
onCompleteBangMessage: (command, exitCode, messageId) => {
|
|
1465
|
+
onCompleteBangMessage: (command, exitCode, messageId, output) => {
|
|
1420
1466
|
this.emit(
|
|
1421
1467
|
"bangMessageCompleted",
|
|
1422
|
-
{ command, exitCode, messageId },
|
|
1468
|
+
{ command, exitCode, messageId, output },
|
|
1423
1469
|
ctx.registeredSessionId,
|
|
1424
1470
|
);
|
|
1425
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
|
|
@@ -2,8 +2,8 @@ import type { Message } from "wave-agent-sdk";
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* 判断一条 user 消息能否作为 /rewind 检查点。
|
|
5
|
-
* 后台任务通知(task_notification
|
|
6
|
-
*
|
|
5
|
+
* 后台任务通知(task_notification)、hook 注入的消息(source: "hook")
|
|
6
|
+
* 与 bang 命令消息都是系统生成、用户不可见的,不能作为回滚点。
|
|
7
7
|
* CLI 交互式选择器与 stdio listRewindCheckpoints 共用此判定,避免两处漂移。
|
|
8
8
|
*/
|
|
9
9
|
export function isUserCheckpointMessage(m: Message): boolean {
|
|
@@ -11,5 +11,6 @@ export function isUserCheckpointMessage(m: Message): boolean {
|
|
|
11
11
|
if (m.blocks.some((b) => b.type === "task_notification")) return false;
|
|
12
12
|
if (m.blocks.some((b) => b.type === "text" && b.source === "hook"))
|
|
13
13
|
return false;
|
|
14
|
+
if (m.blocks.some((b) => b.type === "bang")) return false;
|
|
14
15
|
return true;
|
|
15
16
|
}
|