wave-code 1.0.6 → 1.0.8

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 (54) hide show
  1. package/dist/components/AgentsManager.d.ts +7 -0
  2. package/dist/components/AgentsManager.js +109 -0
  3. package/dist/components/ChatInterface.js +1 -1
  4. package/dist/components/ConfirmationDetails.d.ts +1 -0
  5. package/dist/components/ConfirmationDetails.js +5 -3
  6. package/dist/components/ConfirmationSelector.js +17 -3
  7. package/dist/components/InputBox.js +7 -21
  8. package/dist/components/LoginCommand.js +31 -2
  9. package/dist/components/MarketplaceAddForm.js +16 -2
  10. package/dist/components/RewindCommand.js +11 -4
  11. package/dist/constants/commands.js +6 -0
  12. package/dist/contexts/useChat.d.ts +18 -2
  13. package/dist/contexts/useChat.js +114 -9
  14. package/dist/daemon/commands.d.ts +49 -0
  15. package/dist/daemon/commands.js +341 -0
  16. package/dist/daemon/jsonRpcClient.d.ts +38 -0
  17. package/dist/daemon/jsonRpcClient.js +129 -0
  18. package/dist/daemon/socketClient.d.ts +13 -0
  19. package/dist/daemon/socketClient.js +26 -0
  20. package/dist/hooks/useInputManager.d.ts +2 -0
  21. package/dist/hooks/useInputManager.js +8 -0
  22. package/dist/index.js +88 -0
  23. package/dist/managers/inputHandlers.js +3 -0
  24. package/dist/managers/inputReducer.d.ts +4 -0
  25. package/dist/managers/inputReducer.js +8 -0
  26. package/dist/reducers/agentsManagerReducer.d.ts +26 -0
  27. package/dist/reducers/agentsManagerReducer.js +54 -0
  28. package/dist/stdio/agentBridge.d.ts +15 -0
  29. package/dist/stdio/agentBridge.js +101 -20
  30. package/dist/stdio/protocol.d.ts +1 -1
  31. package/dist/utils/usageSummary.d.ts +0 -4
  32. package/dist/utils/usageSummary.js +1 -34
  33. package/package.json +2 -2
  34. package/src/components/AgentsManager.tsx +290 -0
  35. package/src/components/ChatInterface.tsx +2 -0
  36. package/src/components/ConfirmationDetails.tsx +6 -0
  37. package/src/components/ConfirmationSelector.tsx +18 -3
  38. package/src/components/InputBox.tsx +54 -45
  39. package/src/components/LoginCommand.tsx +35 -2
  40. package/src/components/MarketplaceAddForm.tsx +17 -2
  41. package/src/components/RewindCommand.tsx +10 -4
  42. package/src/constants/commands.ts +6 -0
  43. package/src/contexts/useChat.tsx +146 -7
  44. package/src/daemon/commands.ts +444 -0
  45. package/src/daemon/jsonRpcClient.ts +158 -0
  46. package/src/daemon/socketClient.ts +34 -0
  47. package/src/hooks/useInputManager.ts +8 -0
  48. package/src/index.ts +130 -0
  49. package/src/managers/inputHandlers.ts +2 -0
  50. package/src/managers/inputReducer.ts +10 -0
  51. package/src/reducers/agentsManagerReducer.ts +91 -0
  52. package/src/stdio/agentBridge.ts +123 -19
  53. package/src/stdio/protocol.ts +4 -0
  54. package/src/utils/usageSummary.ts +2 -46
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useReducer } from "react";
1
+ import React, { useEffect, useReducer, useRef } from "react";
2
2
  import { Box, Text, useInput } from "ink";
3
3
  import type {
4
4
  PermissionDecision,
@@ -13,6 +13,7 @@ import {
13
13
  } from "wave-agent-sdk";
14
14
  import { confirmationReducer } from "../reducers/confirmationReducer.js";
15
15
  import { questionReducer } from "../reducers/questionReducer.js";
16
+ import { createBracketedPasteDetector } from "../utils/bracketedPaste.js";
16
17
 
17
18
  const getHeaderColor = (header: string) => {
18
19
  const colors = ["red", "green", "blue", "magenta", "cyan"] as const;
@@ -103,23 +104,37 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
103
104
  return "Yes, and auto-accept edits";
104
105
  };
105
106
 
107
+ const pasteDetectorRef = useRef(createBracketedPasteDetector());
108
+
106
109
  useInput((input, key) => {
107
110
  if (key.escape) {
108
111
  onCancel();
109
112
  return;
110
113
  }
111
114
 
115
+ const result = pasteDetectorRef.current.process(input);
116
+ if (result.kind === "consume") {
117
+ // Content of an in-flight bracketed paste: hold it, never submit.
118
+ return;
119
+ }
120
+ let cleanInput: string;
121
+ if (result.kind === "paste") {
122
+ cleanInput = (result.leadingInput ?? "") + result.text;
123
+ } else {
124
+ cleanInput = result.input;
125
+ }
126
+
112
127
  if (toolName === ASK_USER_QUESTION_TOOL_NAME) {
113
128
  questionDispatch({
114
129
  type: "HANDLE_KEY",
115
- input,
130
+ input: cleanInput,
116
131
  key,
117
132
  questions,
118
133
  });
119
134
  } else {
120
135
  dispatch({
121
136
  type: "HANDLE_KEY",
122
- input,
137
+ input: cleanInput,
123
138
  key,
124
139
  toolName,
125
140
  toolInput,
@@ -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
  });
@@ -34,10 +34,16 @@ export const RewindCommand: React.FC<RewindCommandProps> = ({
34
34
  }, [getFullMessageThread]);
35
35
 
36
36
  // Filter user messages as checkpoints, excluding meta messages and
37
- // system-generated user-role messages (task notifications, hook injections)
38
- const checkpoints = messages
39
- .map((msg, index) => ({ msg, index }))
40
- .filter(({ msg }) => isUserCheckpointMessage(msg));
37
+ // system-generated user-role messages (task notifications, hook injections).
38
+ // Compaction is append-only: the same message id appears twice on the full
39
+ // thread (pre-compact history + post-compact append), so dedupe by id and
40
+ // keep the last occurrence (matching the folded view the user sees).
41
+ const checkpointMap = new Map<string, { msg: Message; index: number }>();
42
+ messages.forEach((msg, index) => {
43
+ if (!isUserCheckpointMessage(msg)) return;
44
+ checkpointMap.set(msg.id ?? `index:${index}`, { msg, index });
45
+ });
46
+ const checkpoints = Array.from(checkpointMap.values());
41
47
 
42
48
  const MAX_VISIBLE_ITEMS = 3;
43
49
 
@@ -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,
@@ -30,7 +31,6 @@ import {
30
31
  extractLatestTotalTokens,
31
32
  } from "wave-agent-sdk";
32
33
  import { logger } from "../utils/logger.js";
33
- import { throttle } from "../utils/throttle.js";
34
34
  import { displayUsageSummary } from "../utils/usageSummary.js";
35
35
  import { expandLongTextPlaceholders } from "../managers/inputHandlers.js";
36
36
 
@@ -96,6 +96,8 @@ export interface ChatContextType {
96
96
  // Slash Command functionality
97
97
  slashCommands: SlashCommand[];
98
98
  hasSlashCommand: (commandId: string) => boolean;
99
+ // Agent definitions (for /agents overlay)
100
+ agentDefinitions: SubagentConfiguration[];
99
101
  // Permission functionality
100
102
  permissionMode: PermissionMode;
101
103
  setPermissionMode: (mode: PermissionMode) => void;
@@ -109,6 +111,7 @@ export interface ChatContextType {
109
111
  hidePersistentOption?: boolean;
110
112
  planContent?: string;
111
113
  permissionMode?: PermissionMode;
114
+ warning?: string;
112
115
  };
113
116
  showConfirmation: (
114
117
  toolName: string,
@@ -117,6 +120,7 @@ export interface ChatContextType {
117
120
  hidePersistentOption?: boolean,
118
121
  planContent?: string,
119
122
  permissionMode?: PermissionMode,
123
+ warning?: string,
120
124
  ) => Promise<PermissionDecision>;
121
125
  hideConfirmation: () => void;
122
126
  handleConfirmationDecision: (decision: PermissionDecision) => void;
@@ -236,6 +240,99 @@ function createStreamingWindowThrottle(
236
240
  return throttled;
237
241
  }
238
242
 
243
+ /**
244
+ * Per-tool window-concat throttle for pure-delta tool parameter streaming:
245
+ * `parametersChunk` deltas are accumulated independently per tool block id
246
+ * within the cooldown window, so interleaved multi-tool streams lose no delta
247
+ * (a plain throttle's single last-args slot would drop every earlier tool's
248
+ * deltas, leaving the first tool without streaming parameters). `start` /
249
+ * `running` apply immediately (one-shot snapshots); `end` flushes pending
250
+ * streaming deltas first, then applies the authoritative parameters/result.
251
+ */
252
+ export function createToolStreamingThrottle(
253
+ fn: (params: ToolBlockUpdateCallbackParams) => void,
254
+ wait: number,
255
+ ): {
256
+ (params: ToolBlockUpdateCallbackParams): void;
257
+ cancel: () => void;
258
+ flush: () => void;
259
+ } {
260
+ let timer: NodeJS.Timeout | null = null;
261
+ let pending: { messageId: string; chunks: Map<string, string> } | null = null;
262
+
263
+ const fire = () => {
264
+ if (pending && pending.chunks.size > 0) {
265
+ const { messageId, chunks } = pending;
266
+ pending = null;
267
+ for (const [id, chunk] of chunks) {
268
+ fn({ messageId, id, parametersChunk: chunk, stage: "streaming" });
269
+ }
270
+ }
271
+ };
272
+
273
+ const throttled = (params: ToolBlockUpdateCallbackParams) => {
274
+ if (params.stage === "end") {
275
+ // Flush any deltas still pending inside the cooldown window first
276
+ if (timer) {
277
+ clearTimeout(timer);
278
+ timer = null;
279
+ }
280
+ fire();
281
+ fn(params);
282
+ return;
283
+ }
284
+ if (params.stage === "streaming") {
285
+ if (!pending) {
286
+ pending = { messageId: params.messageId, chunks: new Map() };
287
+ }
288
+ const prev = pending.chunks.get(params.id) || "";
289
+ pending.chunks.set(params.id, prev + (params.parametersChunk || ""));
290
+ if (!timer) {
291
+ timer = setTimeout(() => {
292
+ timer = null;
293
+ fire();
294
+ }, wait);
295
+ }
296
+ return;
297
+ }
298
+ // start / running — one-shot snapshots applied immediately. Drop this
299
+ // tool's buffered streaming deltas first: start/running carry the
300
+ // authoritative parameters, and a pending timer would otherwise fire late
301
+ // with a stale `streaming` event, regressing this tool block's stage back
302
+ // to streaming (yellow dot -> gray) mid-execution. Other tools' in-flight
303
+ // chunks are kept so interleaved multi-tool streaming still accumulates.
304
+ if (pending) {
305
+ pending.chunks.delete(params.id);
306
+ if (pending.chunks.size === 0) {
307
+ pending = null;
308
+ if (timer) {
309
+ clearTimeout(timer);
310
+ timer = null;
311
+ }
312
+ }
313
+ }
314
+ fn(params);
315
+ };
316
+
317
+ throttled.cancel = () => {
318
+ if (timer) {
319
+ clearTimeout(timer);
320
+ timer = null;
321
+ }
322
+ pending = null;
323
+ };
324
+
325
+ throttled.flush = () => {
326
+ if (timer) {
327
+ clearTimeout(timer);
328
+ timer = null;
329
+ }
330
+ fire();
331
+ };
332
+
333
+ return throttled;
334
+ }
335
+
239
336
  export const ChatProvider: React.FC<ChatProviderProps> = ({
240
337
  children,
241
338
  bypassPermissions,
@@ -342,8 +439,13 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
342
439
 
343
440
  const throttledToolBlockUpdate = useMemo(
344
441
  () =>
345
- throttle((params: ToolBlockUpdateCallbackParams) => {
346
- const { messageId, id: toolBlockId, ...updates } = params;
442
+ createToolStreamingThrottle((params) => {
443
+ const {
444
+ messageId,
445
+ id: toolBlockId,
446
+ parametersChunk,
447
+ ...updates
448
+ } = params;
347
449
  setMessages((prev) =>
348
450
  prev.map((m) => {
349
451
  if (m.id !== messageId) return m;
@@ -360,7 +462,8 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
360
462
  id: toolBlockId,
361
463
  name: updates.name || "",
362
464
  stage: updates.stage || "start",
363
- parameters: updates.parameters || "",
465
+ parameters:
466
+ (updates.parameters || "") + (parametersChunk || ""),
364
467
  result: updates.result || "",
365
468
  ...updates,
366
469
  },
@@ -371,7 +474,18 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
371
474
  ...m,
372
475
  blocks: m.blocks.map((b, idx) =>
373
476
  idx === toolBlockIndex && b.type === "tool"
374
- ? { ...b, ...updates }
477
+ ? {
478
+ ...b,
479
+ ...updates,
480
+ // Streaming carries only the delta; append it to the
481
+ // accumulated parameters. start/running/end carry the
482
+ // authoritative value and replace wholesale.
483
+ parameters: parametersChunk
484
+ ? (b.parameters || "") + parametersChunk
485
+ : updates.parameters !== undefined
486
+ ? updates.parameters
487
+ : b.parameters,
488
+ }
375
489
  : b,
376
490
  ),
377
491
  };
@@ -419,6 +533,11 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
419
533
  // Command state
420
534
  const [slashCommands, setSlashCommands] = useState<SlashCommand[]>([]);
421
535
 
536
+ // Agent definitions (for /agents overlay)
537
+ const [agentDefinitions, setAgentDefinitions] = useState<
538
+ SubagentConfiguration[]
539
+ >([]);
540
+
422
541
  // Permission state
423
542
  const [permissionMode, setPermissionModeState] = useState<PermissionMode>(
424
543
  initialPermissionMode ||
@@ -435,6 +554,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
435
554
  hidePersistentOption?: boolean;
436
555
  planContent?: string;
437
556
  permissionMode?: PermissionMode;
557
+ warning?: string;
438
558
  }
439
559
  | undefined
440
560
  >();
@@ -446,6 +566,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
446
566
  hidePersistentOption?: boolean;
447
567
  planContent?: string;
448
568
  permissionMode?: PermissionMode;
569
+ warning?: string;
449
570
  resolver: (decision: PermissionDecision) => void;
450
571
  reject: () => void;
451
572
  }>
@@ -457,6 +578,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
457
578
  hidePersistentOption?: boolean;
458
579
  planContent?: string;
459
580
  permissionMode?: PermissionMode;
581
+ warning?: string;
460
582
  resolver: (decision: PermissionDecision) => void;
461
583
  reject: () => void;
462
584
  } | null>(null);
@@ -498,6 +620,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
498
620
  hidePersistentOption?: boolean,
499
621
  planContent?: string,
500
622
  permissionMode?: PermissionMode,
623
+ warning?: string,
501
624
  ): Promise<PermissionDecision> => {
502
625
  return new Promise<PermissionDecision>((resolve, reject) => {
503
626
  const queueItem = {
@@ -507,6 +630,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
507
630
  hidePersistentOption,
508
631
  planContent,
509
632
  permissionMode,
633
+ warning,
510
634
  resolver: resolve,
511
635
  reject,
512
636
  };
@@ -627,7 +751,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
627
751
  ),
628
752
  );
629
753
  },
630
- onCompleteBangMessage: (command, exitCode, messageId) => {
754
+ onCompleteBangMessage: (command, exitCode, messageId, output) => {
631
755
  if (isExpandedRef.current) return;
632
756
  setMessages((prev) =>
633
757
  prev.map((m) =>
@@ -636,7 +760,13 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
636
760
  ...m,
637
761
  blocks: m.blocks.map((b, idx) =>
638
762
  idx === m.blocks.length - 1 && b.type === "bang"
639
- ? { ...b, command, exitCode, stage: "end" }
763
+ ? {
764
+ ...b,
765
+ command,
766
+ exitCode,
767
+ stage: "end",
768
+ ...(output !== undefined ? { output } : {}),
769
+ }
640
770
  : b,
641
771
  ),
642
772
  }
@@ -709,6 +839,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
709
839
  context.hidePersistentOption,
710
840
  context.planContent,
711
841
  context.permissionMode,
842
+ context.warning,
712
843
  );
713
844
  } catch {
714
845
  // If confirmation was cancelled or failed, deny the operation
@@ -778,6 +909,11 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
778
909
  // Get initial commands
779
910
  const agentSlashCommands = agent.getSlashCommands?.() || [];
780
911
  setSlashCommands(agentSlashCommands);
912
+
913
+ // Get initial agent definitions
914
+ const initialAgentDefinitions =
915
+ agent.getSubagentConfigurations?.() || [];
916
+ setAgentDefinitions(initialAgentDefinitions);
781
917
  } catch (error) {
782
918
  console.error("Failed to initialize AI manager:", error);
783
919
  }
@@ -818,6 +954,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
818
954
  setMessages([]);
819
955
  setMcpServerStatuses([]);
820
956
  setSlashCommands([]);
957
+ setAgentDefinitions([]);
821
958
  setSessionId("");
822
959
  setIsLoading(false);
823
960
  setLatestTotalTokens(0);
@@ -1050,6 +1187,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
1050
1187
  hidePersistentOption: next.hidePersistentOption,
1051
1188
  planContent: next.planContent,
1052
1189
  permissionMode: next.permissionMode,
1190
+ warning: next.warning,
1053
1191
  });
1054
1192
  setIsConfirmationVisible(true);
1055
1193
  setConfirmationQueue((prev) => prev.slice(1));
@@ -1205,6 +1343,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
1205
1343
  stopBackgroundTask,
1206
1344
  slashCommands,
1207
1345
  hasSlashCommand,
1346
+ agentDefinitions,
1208
1347
  permissionMode,
1209
1348
  setPermissionMode,
1210
1349
  isConfirmationVisible,