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.
Files changed (39) 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 +96 -21
  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 +4 -0
  18. package/dist/stdio/agentBridge.js +45 -10
  19. package/dist/stdio/protocol.d.ts +1 -1
  20. package/dist/utils/rewindCheckpoints.d.ts +2 -2
  21. package/dist/utils/rewindCheckpoints.js +4 -2
  22. package/dist/utils/usageSummary.d.ts +0 -4
  23. package/dist/utils/usageSummary.js +1 -34
  24. package/package.json +2 -2
  25. package/src/components/AgentsManager.tsx +290 -0
  26. package/src/components/ConfirmationSelector.tsx +18 -3
  27. package/src/components/InputBox.tsx +54 -45
  28. package/src/components/LoginCommand.tsx +35 -2
  29. package/src/components/MarketplaceAddForm.tsx +17 -2
  30. package/src/constants/commands.ts +6 -0
  31. package/src/contexts/useChat.tsx +159 -72
  32. package/src/hooks/useInputManager.ts +8 -0
  33. package/src/managers/inputHandlers.ts +2 -0
  34. package/src/managers/inputReducer.ts +10 -0
  35. package/src/reducers/agentsManagerReducer.ts +91 -0
  36. package/src/stdio/agentBridge.ts +55 -9
  37. package/src/stdio/protocol.ts +2 -0
  38. package/src/utils/rewindCheckpoints.ts +3 -2
  39. package/src/utils/usageSummary.ts +2 -46
@@ -0,0 +1,290 @@
1
+ import React, { useEffect, useMemo, useReducer } from "react";
2
+ import { Box, Text, useInput, useStdout } from "ink";
3
+ import type { SubagentConfiguration } from "wave-agent-sdk";
4
+ import { Markdown } from "./Markdown.js";
5
+ import {
6
+ agentsManagerReducer,
7
+ type AgentsManagerState,
8
+ } from "../reducers/agentsManagerReducer.js";
9
+
10
+ export interface AgentsManagerProps {
11
+ onCancel: () => void;
12
+ agentDefinitions: SubagentConfiguration[];
13
+ }
14
+
15
+ interface DisplayEntry {
16
+ kind: "header" | "definition" | "empty";
17
+ label: string;
18
+ sub?: string;
19
+ model?: string;
20
+ scope?: SubagentConfiguration["scope"];
21
+ selectableIndex: number; // -1 for non-selectable rows
22
+ definition?: SubagentConfiguration;
23
+ }
24
+
25
+ const SCOPE_LABELS: Record<SubagentConfiguration["scope"], string> = {
26
+ builtin: "Built-in agents",
27
+ user: "User agents",
28
+ project: "Project agents",
29
+ plugin: "Plugin agents",
30
+ };
31
+
32
+ const SCOPE_ORDER: SubagentConfiguration["scope"][] = [
33
+ "builtin",
34
+ "user",
35
+ "project",
36
+ "plugin",
37
+ ];
38
+
39
+ const initialState: AgentsManagerState = {
40
+ selectedIndex: 0,
41
+ viewMode: "list",
42
+ pendingEffect: null,
43
+ };
44
+
45
+ export const AgentsManager: React.FC<AgentsManagerProps> = ({
46
+ onCancel,
47
+ agentDefinitions,
48
+ }) => {
49
+ const [state, dispatch] = useReducer(agentsManagerReducer, initialState);
50
+ const { stdout } = useStdout();
51
+
52
+ // Handle pending effects
53
+ useEffect(() => {
54
+ if (!state.pendingEffect) return;
55
+ const effect = state.pendingEffect;
56
+ dispatch({ type: "CLEAR_PENDING_EFFECT" });
57
+ if (effect.type === "CANCEL") {
58
+ onCancel();
59
+ }
60
+ }, [state.pendingEffect, onCancel]);
61
+
62
+ // Flatten definitions (grouped by scope) into one navigable list. Headers
63
+ // and the empty-state line are non-selectable.
64
+ const entries = useMemo<DisplayEntry[]>(() => {
65
+ const result: DisplayEntry[] = [];
66
+ let selectableCount = 0;
67
+
68
+ result.push({ kind: "header", label: "AGENTS", selectableIndex: -1 });
69
+ let definitionCount = 0;
70
+ for (const scope of SCOPE_ORDER) {
71
+ const defs = agentDefinitions
72
+ .filter((d) => d.scope === scope)
73
+ .sort((a, b) => a.name.localeCompare(b.name));
74
+ if (defs.length === 0) continue;
75
+ result.push({
76
+ kind: "header",
77
+ label: SCOPE_LABELS[scope],
78
+ scope,
79
+ selectableIndex: -1,
80
+ });
81
+ for (const def of defs) {
82
+ result.push({
83
+ kind: "definition",
84
+ label: def.name,
85
+ model: def.model,
86
+ sub: def.description,
87
+ scope: def.scope,
88
+ selectableIndex: selectableCount++,
89
+ definition: def,
90
+ });
91
+ definitionCount++;
92
+ }
93
+ }
94
+ if (definitionCount === 0) {
95
+ result.push({
96
+ kind: "empty",
97
+ label: "No agents available",
98
+ selectableIndex: -1,
99
+ });
100
+ }
101
+
102
+ return result;
103
+ }, [agentDefinitions]);
104
+
105
+ const itemCount = entries.filter((e) => e.selectableIndex >= 0).length;
106
+
107
+ // Window slice: center the selected item within the visible area, clamping
108
+ // to the terminal's available rows (reusable pattern from
109
+ // BackgroundTaskManager).
110
+ const availableRows = stdout?.rows ?? 24;
111
+ const maxVisible = Math.max(3, Math.min(15, availableRows - 12));
112
+ const selectedFlatIndex = entries.findIndex(
113
+ (e) => e.selectableIndex === state.selectedIndex,
114
+ );
115
+ const startIndex = Math.max(
116
+ 0,
117
+ Math.min(
118
+ selectedFlatIndex - Math.floor(maxVisible / 2),
119
+ Math.max(0, entries.length - maxVisible),
120
+ ),
121
+ );
122
+ const visibleEntries = entries.slice(startIndex, startIndex + maxVisible);
123
+
124
+ useInput((input, key) => {
125
+ dispatch({ type: "HANDLE_KEY", input, key, itemCount });
126
+ });
127
+
128
+ const selectedEntry = entries.find(
129
+ (e) => e.selectableIndex === state.selectedIndex,
130
+ );
131
+
132
+ // Detail view — body renders fully expanded with no height limit, no
133
+ // clipping and no scrolling (aligned with Claude Code's AgentDetail).
134
+ if (state.viewMode === "detail" && selectedEntry) {
135
+ const def = selectedEntry.definition;
136
+ return (
137
+ <Box
138
+ flexDirection="column"
139
+ borderStyle="single"
140
+ borderColor="cyan"
141
+ borderBottom={false}
142
+ borderLeft={false}
143
+ borderRight={false}
144
+ paddingTop={1}
145
+ gap={1}
146
+ >
147
+ <Box>
148
+ <Text color="cyan" bold>
149
+ Agent: {selectedEntry.label}
150
+ </Text>
151
+ </Box>
152
+
153
+ <Box flexDirection="column" gap={1}>
154
+ {def?.description && (
155
+ <Box>
156
+ <Text>
157
+ <Text color="blue">Description:</Text> {def.description}
158
+ </Text>
159
+ </Box>
160
+ )}
161
+ <Box>
162
+ <Text>
163
+ <Text color="blue">Model:</Text>{" "}
164
+ {def?.model || "default (not explicitly configured)"}
165
+ </Text>
166
+ </Box>
167
+ <Box>
168
+ <Text>
169
+ <Text color="blue">Scope:</Text>{" "}
170
+ {def ? SCOPE_LABELS[def.scope] : ""}
171
+ </Text>
172
+ </Box>
173
+ {def?.tools && def.tools.length > 0 && (
174
+ <Box>
175
+ <Text wrap="wrap">
176
+ <Text color="blue">Tools:</Text> {def.tools.join(", ")}
177
+ </Text>
178
+ </Box>
179
+ )}
180
+ {def?.filePath && (
181
+ <Box>
182
+ <Text wrap="wrap">
183
+ <Text color="blue">File:</Text> {def.filePath}
184
+ </Text>
185
+ </Box>
186
+ )}
187
+ </Box>
188
+
189
+ {def && (
190
+ <Box flexDirection="column" marginTop={1}>
191
+ <Text color="blue" bold>
192
+ System Prompt:
193
+ </Text>
194
+ <Box marginLeft={2} marginRight={2}>
195
+ <Markdown>{def.systemPrompt}</Markdown>
196
+ </Box>
197
+ </Box>
198
+ )}
199
+
200
+ <Box marginTop={1}>
201
+ <Text dimColor>Esc or Enter to go back</Text>
202
+ </Box>
203
+ </Box>
204
+ );
205
+ }
206
+
207
+ if (itemCount === 0) {
208
+ return (
209
+ <Box
210
+ flexDirection="column"
211
+ borderStyle="single"
212
+ borderColor="cyan"
213
+ borderBottom={false}
214
+ borderLeft={false}
215
+ borderRight={false}
216
+ paddingTop={1}
217
+ >
218
+ <Text color="cyan" bold>
219
+ Agents
220
+ </Text>
221
+ <Text>No agents available</Text>
222
+ <Text dimColor>Press Escape to close</Text>
223
+ </Box>
224
+ );
225
+ }
226
+
227
+ return (
228
+ <Box
229
+ flexDirection="column"
230
+ borderStyle="single"
231
+ borderColor="cyan"
232
+ borderBottom={false}
233
+ borderLeft={false}
234
+ borderRight={false}
235
+ paddingTop={1}
236
+ gap={1}
237
+ >
238
+ <Box>
239
+ <Text color="cyan" bold>
240
+ Agents
241
+ </Text>
242
+ </Box>
243
+ <Text dimColor>Select an agent to view details</Text>
244
+
245
+ <Box flexDirection="column">
246
+ {visibleEntries.map((entry, index) => {
247
+ const isSelected = entry.selectableIndex === state.selectedIndex;
248
+ if (entry.kind === "header") {
249
+ return (
250
+ <Text key={`${entry.kind}-${entry.label}-${index}`} dimColor bold>
251
+ {entry.label}
252
+ </Text>
253
+ );
254
+ }
255
+ if (entry.kind === "empty") {
256
+ return (
257
+ <Text key={`empty-${index}`} dimColor>
258
+ {entry.label}
259
+ </Text>
260
+ );
261
+ }
262
+ return (
263
+ <Text
264
+ key={`${entry.kind}-${entry.selectableIndex}`}
265
+ color={isSelected ? "black" : "white"}
266
+ backgroundColor={isSelected ? "cyan" : undefined}
267
+ wrap="truncate-end"
268
+ >
269
+ {isSelected ? "▶ " : " "}
270
+ {entry.selectableIndex + 1}. {entry.label}
271
+ {entry.model ? (
272
+ <Text color={isSelected ? "black" : "gray"}>
273
+ {" "}
274
+ · {entry.model}
275
+ </Text>
276
+ ) : null}
277
+ {entry.sub ? ` · ${entry.sub}` : ""}
278
+ </Text>
279
+ );
280
+ })}
281
+ </Box>
282
+
283
+ <Box marginTop={1}>
284
+ <Text dimColor>
285
+ ↑/↓ to select · Enter to view details · Esc to close
286
+ </Text>
287
+ </Box>
288
+ </Box>
289
+ );
290
+ };
@@ -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
  });
@@ -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",