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
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+ import type { SubagentConfiguration } from "wave-agent-sdk";
3
+ export interface AgentsManagerProps {
4
+ onCancel: () => void;
5
+ agentDefinitions: SubagentConfiguration[];
6
+ }
7
+ export declare const AgentsManager: React.FC<AgentsManagerProps>;
@@ -0,0 +1,109 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useReducer } from "react";
3
+ import { Box, Text, useInput, useStdout } from "ink";
4
+ import { Markdown } from "./Markdown.js";
5
+ import { agentsManagerReducer, } from "../reducers/agentsManagerReducer.js";
6
+ const SCOPE_LABELS = {
7
+ builtin: "Built-in agents",
8
+ user: "User agents",
9
+ project: "Project agents",
10
+ plugin: "Plugin agents",
11
+ };
12
+ const SCOPE_ORDER = [
13
+ "builtin",
14
+ "user",
15
+ "project",
16
+ "plugin",
17
+ ];
18
+ const initialState = {
19
+ selectedIndex: 0,
20
+ viewMode: "list",
21
+ pendingEffect: null,
22
+ };
23
+ export const AgentsManager = ({ onCancel, agentDefinitions, }) => {
24
+ const [state, dispatch] = useReducer(agentsManagerReducer, initialState);
25
+ const { stdout } = useStdout();
26
+ // Handle pending effects
27
+ useEffect(() => {
28
+ if (!state.pendingEffect)
29
+ return;
30
+ const effect = state.pendingEffect;
31
+ dispatch({ type: "CLEAR_PENDING_EFFECT" });
32
+ if (effect.type === "CANCEL") {
33
+ onCancel();
34
+ }
35
+ }, [state.pendingEffect, onCancel]);
36
+ // Flatten definitions (grouped by scope) into one navigable list. Headers
37
+ // and the empty-state line are non-selectable.
38
+ const entries = useMemo(() => {
39
+ const result = [];
40
+ let selectableCount = 0;
41
+ result.push({ kind: "header", label: "AGENTS", selectableIndex: -1 });
42
+ let definitionCount = 0;
43
+ for (const scope of SCOPE_ORDER) {
44
+ const defs = agentDefinitions
45
+ .filter((d) => d.scope === scope)
46
+ .sort((a, b) => a.name.localeCompare(b.name));
47
+ if (defs.length === 0)
48
+ continue;
49
+ result.push({
50
+ kind: "header",
51
+ label: SCOPE_LABELS[scope],
52
+ scope,
53
+ selectableIndex: -1,
54
+ });
55
+ for (const def of defs) {
56
+ result.push({
57
+ kind: "definition",
58
+ label: def.name,
59
+ model: def.model,
60
+ sub: def.description,
61
+ scope: def.scope,
62
+ selectableIndex: selectableCount++,
63
+ definition: def,
64
+ });
65
+ definitionCount++;
66
+ }
67
+ }
68
+ if (definitionCount === 0) {
69
+ result.push({
70
+ kind: "empty",
71
+ label: "No agents available",
72
+ selectableIndex: -1,
73
+ });
74
+ }
75
+ return result;
76
+ }, [agentDefinitions]);
77
+ const itemCount = entries.filter((e) => e.selectableIndex >= 0).length;
78
+ // Window slice: center the selected item within the visible area, clamping
79
+ // to the terminal's available rows (reusable pattern from
80
+ // BackgroundTaskManager).
81
+ const availableRows = stdout?.rows ?? 24;
82
+ const maxVisible = Math.max(3, Math.min(15, availableRows - 12));
83
+ const selectedFlatIndex = entries.findIndex((e) => e.selectableIndex === state.selectedIndex);
84
+ const startIndex = Math.max(0, Math.min(selectedFlatIndex - Math.floor(maxVisible / 2), Math.max(0, entries.length - maxVisible)));
85
+ const visibleEntries = entries.slice(startIndex, startIndex + maxVisible);
86
+ useInput((input, key) => {
87
+ dispatch({ type: "HANDLE_KEY", input, key, itemCount });
88
+ });
89
+ const selectedEntry = entries.find((e) => e.selectableIndex === state.selectedIndex);
90
+ // Detail view — body renders fully expanded with no height limit, no
91
+ // clipping and no scrolling (aligned with Claude Code's AgentDetail).
92
+ if (state.viewMode === "detail" && selectedEntry) {
93
+ const def = selectedEntry.definition;
94
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", borderBottom: false, borderLeft: false, borderRight: false, paddingTop: 1, gap: 1, children: [_jsx(Box, { children: _jsxs(Text, { color: "cyan", bold: true, children: ["Agent: ", selectedEntry.label] }) }), _jsxs(Box, { flexDirection: "column", gap: 1, children: [def?.description && (_jsx(Box, { children: _jsxs(Text, { children: [_jsx(Text, { color: "blue", children: "Description:" }), " ", def.description] }) })), _jsx(Box, { children: _jsxs(Text, { children: [_jsx(Text, { color: "blue", children: "Model:" }), " ", def?.model || "default (not explicitly configured)"] }) }), _jsx(Box, { children: _jsxs(Text, { children: [_jsx(Text, { color: "blue", children: "Scope:" }), " ", def ? SCOPE_LABELS[def.scope] : ""] }) }), def?.tools && def.tools.length > 0 && (_jsx(Box, { children: _jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: "blue", children: "Tools:" }), " ", def.tools.join(", ")] }) })), def?.filePath && (_jsx(Box, { children: _jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: "blue", children: "File:" }), " ", def.filePath] }) }))] }), def && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: "blue", bold: true, children: "System Prompt:" }), _jsx(Box, { marginLeft: 2, marginRight: 2, children: _jsx(Markdown, { children: def.systemPrompt }) })] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Esc or Enter to go back" }) })] }));
95
+ }
96
+ if (itemCount === 0) {
97
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", borderBottom: false, borderLeft: false, borderRight: false, paddingTop: 1, children: [_jsx(Text, { color: "cyan", bold: true, children: "Agents" }), _jsx(Text, { children: "No agents available" }), _jsx(Text, { dimColor: true, children: "Press Escape to close" })] }));
98
+ }
99
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", borderBottom: false, borderLeft: false, borderRight: false, paddingTop: 1, gap: 1, children: [_jsx(Box, { children: _jsx(Text, { color: "cyan", bold: true, children: "Agents" }) }), _jsx(Text, { dimColor: true, children: "Select an agent to view details" }), _jsx(Box, { flexDirection: "column", children: visibleEntries.map((entry, index) => {
100
+ const isSelected = entry.selectableIndex === state.selectedIndex;
101
+ if (entry.kind === "header") {
102
+ return (_jsx(Text, { dimColor: true, bold: true, children: entry.label }, `${entry.kind}-${entry.label}-${index}`));
103
+ }
104
+ if (entry.kind === "empty") {
105
+ return (_jsx(Text, { dimColor: true, children: entry.label }, `empty-${index}`));
106
+ }
107
+ return (_jsxs(Text, { color: isSelected ? "black" : "white", backgroundColor: isSelected ? "cyan" : undefined, wrap: "truncate-end", children: [isSelected ? "▶ " : " ", entry.selectableIndex + 1, ". ", entry.label, entry.model ? (_jsxs(Text, { color: isSelected ? "black" : "gray", children: [" ", "\u00B7 ", entry.model] })) : null, entry.sub ? ` · ${entry.sub}` : ""] }, `${entry.kind}-${entry.selectableIndex}`));
108
+ }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "\u2191/\u2193 to select \u00B7 Enter to view details \u00B7 Esc to close" }) })] }));
109
+ };
@@ -57,5 +57,5 @@ export const ChatInterface = () => {
57
57
  ]);
58
58
  if (!sessionId)
59
59
  return null;
60
- return (_jsxs(Box, { ref: chatInterfaceRef, flexDirection: "column", children: [_jsx(MessageList, { messages: displayMessages, isExpanded: isExpanded, version: version, workdir: workdir, forceStatic: forceStatic }, remountKey), !isConfirmationVisible && !isExpanded && (_jsxs(_Fragment, { children: [(isLoading || isCommandRunning || isCompacting) && (_jsx(LoadingIndicator, { isLoading: isLoading, isCommandRunning: isCommandRunning, isCompacting: isCompacting, latestTotalTokens: latestTotalTokens })), _jsx(TaskList, {}), _jsx(QueuedMessageList, {}), _jsx(InputBox, { isLoading: isLoading, isCommandRunning: isCommandRunning, isCompacting: isCompacting, sendMessage: sendMessage, abortMessage: abortMessage, mcpServers: mcpServers, connectMcpServer: connectMcpServer, disconnectMcpServer: disconnectMcpServer, slashCommands: slashCommands, hasSlashCommand: hasSlashCommand, latestTotalTokens: latestTotalTokens, maxInputTokens: maxInputTokens, showLoginHint: showLoginHint })] })), isConfirmationVisible && (_jsxs(_Fragment, { children: [forceStatic ? (_jsx(Static, { items: [{ key: "confirmation-details" }], children: () => (_jsx(ConfirmationDetails, { toolName: confirmingTool.name, toolInput: confirmingTool.input, planContent: confirmingTool.planContent, isExpanded: isExpanded }, "confirmation-details")) })) : (_jsx(ConfirmationDetails, { toolName: confirmingTool.name, toolInput: confirmingTool.input, planContent: confirmingTool.planContent, isExpanded: isExpanded })), _jsx(ConfirmationSelector, { toolName: confirmingTool.name, toolInput: confirmingTool.input, suggestedPrefix: confirmingTool.suggestedPrefix, hidePersistentOption: confirmingTool.hidePersistentOption, permissionMode: confirmingTool.permissionMode, isExpanded: isExpanded, onDecision: handleConfirmationDecision, onCancel: handleConfirmationCancel })] }))] }));
60
+ return (_jsxs(Box, { ref: chatInterfaceRef, flexDirection: "column", children: [_jsx(MessageList, { messages: displayMessages, isExpanded: isExpanded, version: version, workdir: workdir, forceStatic: forceStatic }, remountKey), !isConfirmationVisible && !isExpanded && (_jsxs(_Fragment, { children: [(isLoading || isCommandRunning || isCompacting) && (_jsx(LoadingIndicator, { isLoading: isLoading, isCommandRunning: isCommandRunning, isCompacting: isCompacting, latestTotalTokens: latestTotalTokens })), _jsx(TaskList, {}), _jsx(QueuedMessageList, {}), _jsx(InputBox, { isLoading: isLoading, isCommandRunning: isCommandRunning, isCompacting: isCompacting, sendMessage: sendMessage, abortMessage: abortMessage, mcpServers: mcpServers, connectMcpServer: connectMcpServer, disconnectMcpServer: disconnectMcpServer, slashCommands: slashCommands, hasSlashCommand: hasSlashCommand, latestTotalTokens: latestTotalTokens, maxInputTokens: maxInputTokens, showLoginHint: showLoginHint })] })), isConfirmationVisible && (_jsxs(_Fragment, { children: [forceStatic ? (_jsx(Static, { items: [{ key: "confirmation-details" }], children: () => (_jsx(ConfirmationDetails, { toolName: confirmingTool.name, toolInput: confirmingTool.input, planContent: confirmingTool.planContent, warning: confirmingTool.warning, isExpanded: isExpanded }, "confirmation-details")) })) : (_jsx(ConfirmationDetails, { toolName: confirmingTool.name, toolInput: confirmingTool.input, planContent: confirmingTool.planContent, warning: confirmingTool.warning, isExpanded: isExpanded })), _jsx(ConfirmationSelector, { toolName: confirmingTool.name, toolInput: confirmingTool.input, suggestedPrefix: confirmingTool.suggestedPrefix, hidePersistentOption: confirmingTool.hidePersistentOption, permissionMode: confirmingTool.permissionMode, isExpanded: isExpanded, onDecision: handleConfirmationDecision, onCancel: handleConfirmationCancel })] }))] }));
61
61
  };
@@ -3,6 +3,7 @@ export interface ConfirmationDetailsProps {
3
3
  toolName: string;
4
4
  toolInput?: Record<string, unknown>;
5
5
  planContent?: string;
6
+ warning?: string;
6
7
  isExpanded?: boolean;
7
8
  }
8
9
  export declare const ConfirmationDetails: React.FC<ConfirmationDetailsProps>;
@@ -1,6 +1,6 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Box, Text } from "ink";
3
- import { BASH_TOOL_NAME, EDIT_TOOL_NAME, WRITE_TOOL_NAME, EXIT_PLAN_MODE_TOOL_NAME, ENTER_PLAN_MODE_TOOL_NAME, ASK_USER_QUESTION_TOOL_NAME, } from "wave-agent-sdk";
3
+ import { BASH_TOOL_NAME, EDIT_TOOL_NAME, WRITE_TOOL_NAME, EXIT_PLAN_MODE_TOOL_NAME, ENTER_PLAN_MODE_TOOL_NAME, ASK_USER_QUESTION_TOOL_NAME, ARTIFACT_TOOL_NAME, } from "wave-agent-sdk";
4
4
  import { DiffDisplay } from "./DiffDisplay.js";
5
5
  import { PlanDisplay } from "./PlanDisplay.js";
6
6
  import { highlightToAnsi } from "../utils/highlightUtils.js";
@@ -22,14 +22,16 @@ const getActionDescription = (toolName, toolInput) => {
22
22
  return "Enter plan mode for complex task planning";
23
23
  case ASK_USER_QUESTION_TOOL_NAME:
24
24
  return "Answer questions to clarify intent";
25
+ case ARTIFACT_TOOL_NAME:
26
+ return `Publish file: ${toolInput.file_path || "unknown file"}`;
25
27
  default:
26
28
  return "Execute operation";
27
29
  }
28
30
  };
29
- export const ConfirmationDetails = ({ toolName, toolInput, planContent, isExpanded = false, }) => {
31
+ export const ConfirmationDetails = ({ toolName, toolInput, planContent, warning, isExpanded = false, }) => {
30
32
  const startLineNumber = toolInput?.startLineNumber ??
31
33
  (toolName === WRITE_TOOL_NAME ? 1 : undefined);
32
- const content = (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "yellow", borderBottom: false, borderLeft: false, borderRight: false, children: [_jsxs(Text, { color: "yellow", bold: true, children: ["Tool: ", toolName] }), _jsx(Text, { color: "yellow", children: getActionDescription(toolName, toolInput) }), _jsx(DiffDisplay, { toolName: toolName, parameters: JSON.stringify(toolInput), startLineNumber: startLineNumber }), toolName !== WRITE_TOOL_NAME &&
34
+ const content = (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "yellow", borderBottom: false, borderLeft: false, borderRight: false, children: [_jsxs(Text, { color: "yellow", bold: true, children: ["Tool: ", toolName] }), _jsx(Text, { color: "yellow", children: getActionDescription(toolName, toolInput) }), warning && _jsxs(Text, { color: "red", children: ["\u26A0 ", warning] }), _jsx(DiffDisplay, { toolName: toolName, parameters: JSON.stringify(toolInput), startLineNumber: startLineNumber }), toolName !== WRITE_TOOL_NAME &&
33
35
  toolName !== EDIT_TOOL_NAME &&
34
36
  toolName !== EXIT_PLAN_MODE_TOOL_NAME &&
35
37
  toolName !== ENTER_PLAN_MODE_TOOL_NAME &&
@@ -1,9 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useEffect, useReducer } from "react";
2
+ import { useEffect, useReducer, useRef } from "react";
3
3
  import { Box, Text, useInput } from "ink";
4
4
  import { BASH_TOOL_NAME, EXIT_PLAN_MODE_TOOL_NAME, ENTER_PLAN_MODE_TOOL_NAME, ASK_USER_QUESTION_TOOL_NAME, } from "wave-agent-sdk";
5
5
  import { confirmationReducer } from "../reducers/confirmationReducer.js";
6
6
  import { questionReducer } from "../reducers/questionReducer.js";
7
+ import { createBracketedPasteDetector } from "../utils/bracketedPaste.js";
7
8
  const getHeaderColor = (header) => {
8
9
  const colors = ["red", "green", "blue", "magenta", "cyan"];
9
10
  let hash = 0;
@@ -64,15 +65,28 @@ export const ConfirmationSelector = ({ toolName, toolInput, suggestedPrefix, hid
64
65
  }
65
66
  return "Yes, and auto-accept edits";
66
67
  };
68
+ const pasteDetectorRef = useRef(createBracketedPasteDetector());
67
69
  useInput((input, key) => {
68
70
  if (key.escape) {
69
71
  onCancel();
70
72
  return;
71
73
  }
74
+ const result = pasteDetectorRef.current.process(input);
75
+ if (result.kind === "consume") {
76
+ // Content of an in-flight bracketed paste: hold it, never submit.
77
+ return;
78
+ }
79
+ let cleanInput;
80
+ if (result.kind === "paste") {
81
+ cleanInput = (result.leadingInput ?? "") + result.text;
82
+ }
83
+ else {
84
+ cleanInput = result.input;
85
+ }
72
86
  if (toolName === ASK_USER_QUESTION_TOOL_NAME) {
73
87
  questionDispatch({
74
88
  type: "HANDLE_KEY",
75
- input,
89
+ input: cleanInput,
76
90
  key,
77
91
  questions,
78
92
  });
@@ -80,7 +94,7 @@ export const ConfirmationSelector = ({ toolName, toolInput, suggestedPrefix, hid
80
94
  else {
81
95
  dispatch({
82
96
  type: "HANDLE_KEY",
83
- input,
97
+ input: cleanInput,
84
98
  key,
85
99
  toolName,
86
100
  toolInput,
@@ -7,6 +7,7 @@ import { CommandSelector } from "./CommandSelector.js";
7
7
  import { HistorySearch } from "./HistorySearch.js";
8
8
  import { BackgroundTaskManager } from "./BackgroundTaskManager.js";
9
9
  import { McpManager } from "./McpManager.js";
10
+ import { AgentsManager } from "./AgentsManager.js";
10
11
  import { RewindCommand } from "./RewindCommand.js";
11
12
  import { HelpView } from "./HelpView.js";
12
13
  import { StatusCommand } from "./StatusCommand.js";
@@ -22,7 +23,7 @@ import { useChat } from "../contexts/useChat.js";
22
23
  export const INPUT_PLACEHOLDER_TEXT = "Type your message (use /help for more info)...";
23
24
  export const INPUT_PLACEHOLDER_TEXT_PREFIX = INPUT_PLACEHOLDER_TEXT.substring(0, 10);
24
25
  export const InputBox = ({ isLoading, isCommandRunning, isCompacting, sendMessage = () => { }, abortMessage = () => { }, mcpServers = [], connectMcpServer = async () => false, disconnectMcpServer = async () => false, slashCommands = [], hasSlashCommand = () => false, latestTotalTokens = 0, maxInputTokens = 200000, showLoginHint = false, }) => {
25
- const { permissionMode: chatPermissionMode, setPermissionMode: setChatPermissionMode, handleRewindSelect, backgroundCurrentTask, messages, getFullMessageThread, sessionId, workingDirectory, askBtw, clearMessages, compact, addDir, currentModel, configuredModels, setModel, recreateAgent, recallQueuedMessage, queuedMessages, setIsBtwActive, } = useChat();
26
+ const { permissionMode: chatPermissionMode, setPermissionMode: setChatPermissionMode, handleRewindSelect, backgroundCurrentTask, messages, getFullMessageThread, sessionId, workingDirectory, askBtw, clearMessages, compact, addDir, currentModel, configuredModels, setModel, recreateAgent, recallQueuedMessage, queuedMessages, setIsBtwActive, agentDefinitions, } = useChat();
26
27
  // Ref to hold setInputText so queue callbacks can access it before useInputManager returns
27
28
  const setInputTextRef = useRef(() => { });
28
29
  const hasQueuedMessages = (queuedMessages?.length ?? 0) > 0;
@@ -45,7 +46,7 @@ export const InputBox = ({ isLoading, isCommandRunning, isCompacting, sendMessag
45
46
  // History search
46
47
  showHistorySearch, historySearchQuery,
47
48
  // Task/MCP Manager
48
- showBackgroundTaskManager, showMcpManager, showRewindManager, showHelp, showStatusCommand, showLoginCommand, showPluginManager, showModelSelector, showWorkflowManager, setShowBackgroundTaskManager, setShowMcpManager, setShowRewindManager, setShowHelp, setShowStatusCommand, setShowLoginCommand, setShowPluginManager, setShowModelSelector, setShowWorkflowManager,
49
+ showBackgroundTaskManager, showMcpManager, showAgentsManager, showRewindManager, showHelp, showStatusCommand, showLoginCommand, showPluginManager, showModelSelector, showWorkflowManager, setShowBackgroundTaskManager, setShowMcpManager, setShowAgentsManager, setShowRewindManager, setShowHelp, setShowStatusCommand, setShowLoginCommand, setShowPluginManager, setShowModelSelector, setShowWorkflowManager,
49
50
  // Permission mode
50
51
  permissionMode, setPermissionMode,
51
52
  // BTW state
@@ -101,6 +102,7 @@ export const InputBox = ({ isLoading, isCommandRunning, isCompacting, sendMessag
101
102
  showModelSelector ||
102
103
  showBackgroundTaskManager ||
103
104
  showMcpManager ||
105
+ showAgentsManager ||
104
106
  showWorkflowManager) {
105
107
  return;
106
108
  }
@@ -132,32 +134,16 @@ export const InputBox = ({ isLoading, isCommandRunning, isCompacting, sendMessag
132
134
  }
133
135
  await handleRewindSelect(index);
134
136
  };
135
- if (showRewindManager) {
136
- return (_jsx(RewindCommand, { messages: messages, onSelect: handleRewindSelectWithClose, onCancel: handleRewindCancel, getFullMessageThread: getFullMessageThread }));
137
- }
138
- if (showHelp) {
139
- return (_jsx(HelpView, { onCancel: () => setShowHelp(false), commands: slashCommands }));
140
- }
141
- if (showStatusCommand) {
142
- return _jsx(StatusCommand, { onCancel: () => setShowStatusCommand(false) });
143
- }
144
- if (showLoginCommand) {
145
- return _jsx(LoginCommand, { onCancel: () => setShowLoginCommand(false) });
146
- }
147
- if (showPluginManager) {
148
- return (_jsx(PluginManagerShell, { onCancel: () => setShowPluginManager(false), onPluginInstalled: recreateAgent }));
149
- }
150
- if (showModelSelector) {
151
- return (_jsx(ModelSelector, { onCancel: () => setShowModelSelector(false), currentModel: currentModel, configuredModels: configuredModels, onSelectModel: setModel }));
152
- }
153
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(BtwDisplay, { btwState: btwState }), showFileSelector && (_jsx(FileSelector, { files: filteredFiles, searchQuery: searchQuery, isLoading: isFileSearching, onSelect: handleFileSelect, onCancel: handleCancelFileSelect })), showCommandSelector && (_jsx(CommandSelector, { searchQuery: commandSearchQuery, onSelect: handleCommandSelect, onInsert: handleCommandInsert, onCancel: handleCancelCommandSelect, commands: slashCommands })), showHistorySearch && (_jsx(HistorySearch, { searchQuery: historySearchQuery, onSelect: handleHistorySearchSelect, onCancel: handleCancelHistorySearch })), showBackgroundTaskManager && (_jsx(BackgroundTaskManager, { onCancel: () => setShowBackgroundTaskManager(false) })), showMcpManager && (_jsx(McpManager, { onCancel: () => setShowMcpManager(false), servers: mcpServers, onConnectServer: connectMcpServer, onDisconnectServer: disconnectMcpServer })), showWorkflowManager && (_jsx(WorkflowManager, { onCancel: () => setShowWorkflowManager(false) })), btwState.question || btwState.answer
137
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(BtwDisplay, { btwState: btwState }), showFileSelector && (_jsx(FileSelector, { files: filteredFiles, searchQuery: searchQuery, isLoading: isFileSearching, onSelect: handleFileSelect, onCancel: handleCancelFileSelect })), showCommandSelector && (_jsx(CommandSelector, { searchQuery: commandSearchQuery, onSelect: handleCommandSelect, onInsert: handleCommandInsert, onCancel: handleCancelCommandSelect, commands: slashCommands })), showHistorySearch && (_jsx(HistorySearch, { searchQuery: historySearchQuery, onSelect: handleHistorySearchSelect, onCancel: handleCancelHistorySearch })), showBackgroundTaskManager && (_jsx(BackgroundTaskManager, { onCancel: () => setShowBackgroundTaskManager(false) })), showMcpManager && (_jsx(McpManager, { onCancel: () => setShowMcpManager(false), servers: mcpServers, onConnectServer: connectMcpServer, onDisconnectServer: disconnectMcpServer })), showAgentsManager && (_jsx(AgentsManager, { onCancel: () => setShowAgentsManager(false), agentDefinitions: agentDefinitions })), showWorkflowManager && (_jsx(WorkflowManager, { onCancel: () => setShowWorkflowManager(false) })), showRewindManager && (_jsx(RewindCommand, { messages: messages, onSelect: handleRewindSelectWithClose, onCancel: handleRewindCancel, getFullMessageThread: getFullMessageThread })), showHelp && (_jsx(HelpView, { onCancel: () => setShowHelp(false), commands: slashCommands })), showStatusCommand && (_jsx(StatusCommand, { onCancel: () => setShowStatusCommand(false) })), showLoginCommand && (_jsx(LoginCommand, { onCancel: () => setShowLoginCommand(false) })), showPluginManager && (_jsx(PluginManagerShell, { onCancel: () => setShowPluginManager(false), onPluginInstalled: recreateAgent })), showModelSelector && (_jsx(ModelSelector, { onCancel: () => setShowModelSelector(false), currentModel: currentModel, configuredModels: configuredModels, onSelectModel: setModel })), btwState.question || btwState.answer
154
138
  ? null
155
139
  : showBackgroundTaskManager ||
156
140
  showMcpManager ||
141
+ showAgentsManager ||
157
142
  showRewindManager ||
158
143
  showHelp ||
159
144
  showStatusCommand ||
160
145
  showLoginCommand ||
161
146
  showPluginManager ||
147
+ showModelSelector ||
162
148
  showWorkflowManager || (_jsxs(Box, { flexDirection: "column", children: [escClearPending && _jsx(Text, { color: "gray", children: "\u518D\u6B21\u6309 Esc \u6E05\u7A7A\u8F93\u5165" }), _jsx(Box, { borderStyle: "single", borderColor: "gray", borderLeft: false, borderRight: false, children: _jsx(Text, { color: isPlaceholder ? "gray" : "white", children: shouldShowCursor ? (_jsxs(_Fragment, { children: [beforeCursor, _jsx(Text, { backgroundColor: "white", color: "black", children: atCursor }), afterCursor] })) : (displayText) }) }), _jsxs(Box, { justifyContent: "space-between", children: [_jsx(StatusLine, { permissionMode: permissionMode, isShellCommand: isShellCommand }), _jsx(Notifications, { latestTotalTokens: latestTotalTokens, maxInputTokens: maxInputTokens, showLoginHint: showLoginHint })] })] }))] }));
163
149
  };
@@ -4,6 +4,7 @@ import { Box, Text, useInput } from "ink";
4
4
  import { execFile } from "child_process";
5
5
  import { promisify } from "util";
6
6
  import { authService } from "wave-agent-sdk";
7
+ import { createBracketedPasteDetector } from "../utils/bracketedPaste.js";
7
8
  const execFileAsync = promisify(execFile);
8
9
  function openBrowser(url) {
9
10
  const platform = process.platform;
@@ -26,6 +27,11 @@ export const LoginCommand = ({ onCancel }) => {
26
27
  const [tokenInput, setTokenInput] = useState("");
27
28
  const isLoadingRef = useRef(isLoading);
28
29
  isLoadingRef.current = isLoading;
30
+ // Detects and strips bracketed paste markers (\x1b[200~ ... \x1b[201~)
31
+ // that terminals wrap pasted text in. Unlike the main InputBox pipeline,
32
+ // this overlay's token input goes through raw ink useInput, which only
33
+ // strips ONE leading ESC, leaving the markers (e.g. "[200~") in the input.
34
+ const pasteDetectorRef = useRef(createBracketedPasteDetector());
29
35
  // Resolve/reject refs for the token promise
30
36
  const tokenResolveRef = useRef(null);
31
37
  const tokenRejectRef = useRef(null);
@@ -64,9 +70,31 @@ export const LoginCommand = ({ onCancel }) => {
64
70
  setTokenInput((prev) => prev.slice(0, -1));
65
71
  return;
66
72
  }
67
- // Regular character input (single or pasted multi-char)
73
+ // Regular character input (single or pasted multi-char). Run through the
74
+ // bracketed paste detector: a pasted token arrives wrapped in
75
+ // \x1b[200~ ... \x1b[201~ markers (possibly split across chunks), which
76
+ // must be stripped instead of being appended to the token.
68
77
  if (input && !key.ctrl && !key.meta && !key.return && input.length > 0) {
69
- setTokenInput((prev) => prev + input);
78
+ const result = pasteDetectorRef.current.process(input);
79
+ if (result.kind === "consume") {
80
+ // In-flight bracketed paste content: hold it; the final chunk
81
+ // delivers the complete text.
82
+ return;
83
+ }
84
+ if (result.kind === "paste") {
85
+ // Tokens never contain carriage returns — drop \r (CRLF terminals
86
+ // send \r, not \n, in pasted text).
87
+ const leading = result.leadingInput?.replace(/\r/g, "");
88
+ if (leading) {
89
+ setTokenInput((prev) => prev + leading);
90
+ }
91
+ const text = result.text.replace(/\r/g, "");
92
+ if (text) {
93
+ setTokenInput((prev) => prev + text);
94
+ }
95
+ return;
96
+ }
97
+ setTokenInput((prev) => prev + result.input);
70
98
  }
71
99
  });
72
100
  const handleEnter = async () => {
@@ -84,6 +112,7 @@ export const LoginCommand = ({ onCancel }) => {
84
112
  setError("");
85
113
  setAuthUrl("");
86
114
  setTokenInput("");
115
+ pasteDetectorRef.current.reset();
87
116
  setMessage("Starting authentication...");
88
117
  // Promise that resolves when user presses Enter with token input
89
118
  const readToken = () => new Promise((resolve, reject) => {
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useReducer, useEffect } from "react";
2
+ import { useReducer, useEffect, useRef } from "react";
3
3
  import { Box, Text, useInput } from "ink";
4
4
  import { usePluginManagerContext } from "../contexts/PluginManagerContext.js";
5
5
  import { marketplaceAddFormReducer, SCOPES, } from "../reducers/marketplaceAddFormReducer.js";
6
+ import { createBracketedPasteDetector } from "../utils/bracketedPaste.js";
6
7
  export const MarketplaceAddForm = () => {
7
8
  const { state: ctxState, actions } = usePluginManagerContext();
8
9
  const [state, dispatch] = useReducer(marketplaceAddFormReducer, {
@@ -23,11 +24,24 @@ export const MarketplaceAddForm = () => {
23
24
  }
24
25
  dispatch({ type: "CLEAR_PENDING_ACTION" });
25
26
  }, [state.pendingAction, actions]);
27
+ const pasteDetectorRef = useRef(createBracketedPasteDetector());
26
28
  useInput((input, key) => {
29
+ const result = pasteDetectorRef.current.process(input);
30
+ if (result.kind === "consume") {
31
+ // Content of an in-flight bracketed paste: hold it, never submit.
32
+ return;
33
+ }
34
+ let cleanInput;
35
+ if (result.kind === "paste") {
36
+ cleanInput = (result.leadingInput ?? "") + result.text;
37
+ }
38
+ else {
39
+ cleanInput = result.input;
40
+ }
27
41
  dispatch({
28
42
  type: "HANDLE_KEY",
29
43
  key,
30
- input,
44
+ input: cleanInput,
31
45
  isLoading: ctxState.isLoading,
32
46
  });
33
47
  });
@@ -16,10 +16,17 @@ export const RewindCommand = ({ messages: initialMessages, onSelect, onCancel, g
16
16
  }
17
17
  }, [getFullMessageThread]);
18
18
  // Filter user messages as checkpoints, excluding meta messages and
19
- // system-generated user-role messages (task notifications, hook injections)
20
- const checkpoints = messages
21
- .map((msg, index) => ({ msg, index }))
22
- .filter(({ msg }) => isUserCheckpointMessage(msg));
19
+ // system-generated user-role messages (task notifications, hook injections).
20
+ // Compaction is append-only: the same message id appears twice on the full
21
+ // thread (pre-compact history + post-compact append), so dedupe by id and
22
+ // keep the last occurrence (matching the folded view the user sees).
23
+ const checkpointMap = new Map();
24
+ messages.forEach((msg, index) => {
25
+ if (!isUserCheckpointMessage(msg))
26
+ return;
27
+ checkpointMap.set(msg.id ?? `index:${index}`, { msg, index });
28
+ });
29
+ const checkpoints = Array.from(checkpointMap.values());
23
30
  const MAX_VISIBLE_ITEMS = 3;
24
31
  const [state, dispatch] = useReducer(rewindSelectorReducer, {
25
32
  selectedIndex: checkpoints.length - 1,
@@ -11,6 +11,12 @@ export const AVAILABLE_COMMANDS = [
11
11
  description: "View and manage MCP servers",
12
12
  handler: () => { }, // Handler here won't be used, actual processing is in the hook
13
13
  },
14
+ {
15
+ id: "agents",
16
+ name: "agents",
17
+ description: "List available agents and active subagents",
18
+ handler: () => { }, // Handler here won't be used, actual processing is in the hook
19
+ },
14
20
  {
15
21
  id: "rewind",
16
22
  name: "rewind",
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import type { Message, McpServerStatus, BackgroundTask, Task, SlashCommand, PermissionDecision, PermissionMode, QueuedMessage, WorkflowRun } from "wave-agent-sdk";
2
+ import type { Message, McpServerStatus, BackgroundTask, Task, SlashCommand, SubagentConfiguration, PermissionDecision, PermissionMode, QueuedMessage, WorkflowRun, ToolBlockUpdateCallbackParams } from "wave-agent-sdk";
3
3
  import { BaseAppProps } from "../types.js";
4
4
  export interface ChatContextType {
5
5
  messages: Message[];
@@ -46,6 +46,7 @@ export interface ChatContextType {
46
46
  stopBackgroundTask: (taskId: string) => boolean;
47
47
  slashCommands: SlashCommand[];
48
48
  hasSlashCommand: (commandId: string) => boolean;
49
+ agentDefinitions: SubagentConfiguration[];
49
50
  permissionMode: PermissionMode;
50
51
  setPermissionMode: (mode: PermissionMode) => void;
51
52
  isConfirmationVisible: boolean;
@@ -57,8 +58,9 @@ export interface ChatContextType {
57
58
  hidePersistentOption?: boolean;
58
59
  planContent?: string;
59
60
  permissionMode?: PermissionMode;
61
+ warning?: string;
60
62
  };
61
- showConfirmation: (toolName: string, toolInput?: Record<string, unknown>, suggestedPrefix?: string, hidePersistentOption?: boolean, planContent?: string, permissionMode?: PermissionMode) => Promise<PermissionDecision>;
63
+ showConfirmation: (toolName: string, toolInput?: Record<string, unknown>, suggestedPrefix?: string, hidePersistentOption?: boolean, planContent?: string, permissionMode?: PermissionMode, warning?: string) => Promise<PermissionDecision>;
62
64
  hideConfirmation: () => void;
63
65
  handleConfirmationDecision: (decision: PermissionDecision) => void;
64
66
  handleConfirmationCancel: () => void;
@@ -82,4 +84,18 @@ export declare const useChat: () => ChatContextType;
82
84
  export interface ChatProviderProps extends BaseAppProps {
83
85
  children: React.ReactNode;
84
86
  }
87
+ /**
88
+ * Per-tool window-concat throttle for pure-delta tool parameter streaming:
89
+ * `parametersChunk` deltas are accumulated independently per tool block id
90
+ * within the cooldown window, so interleaved multi-tool streams lose no delta
91
+ * (a plain throttle's single last-args slot would drop every earlier tool's
92
+ * deltas, leaving the first tool without streaming parameters). `start` /
93
+ * `running` apply immediately (one-shot snapshots); `end` flushes pending
94
+ * streaming deltas first, then applies the authoritative parameters/result.
95
+ */
96
+ export declare function createToolStreamingThrottle(fn: (params: ToolBlockUpdateCallbackParams) => void, wait: number): {
97
+ (params: ToolBlockUpdateCallbackParams): void;
98
+ cancel: () => void;
99
+ flush: () => void;
100
+ };
85
101
  export declare const ChatProvider: React.FC<ChatProviderProps>;