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.
- package/dist/components/AgentsManager.d.ts +7 -0
- package/dist/components/AgentsManager.js +109 -0
- package/dist/components/ChatInterface.js +1 -1
- package/dist/components/ConfirmationDetails.d.ts +1 -0
- package/dist/components/ConfirmationDetails.js +5 -3
- 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/components/RewindCommand.js +11 -4
- package/dist/constants/commands.js +6 -0
- package/dist/contexts/useChat.d.ts +18 -2
- package/dist/contexts/useChat.js +114 -9
- package/dist/daemon/commands.d.ts +49 -0
- package/dist/daemon/commands.js +341 -0
- package/dist/daemon/jsonRpcClient.d.ts +38 -0
- package/dist/daemon/jsonRpcClient.js +129 -0
- package/dist/daemon/socketClient.d.ts +13 -0
- package/dist/daemon/socketClient.js +26 -0
- package/dist/hooks/useInputManager.d.ts +2 -0
- package/dist/hooks/useInputManager.js +8 -0
- package/dist/index.js +88 -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 +15 -0
- package/dist/stdio/agentBridge.js +101 -20
- package/dist/stdio/protocol.d.ts +1 -1
- 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/ChatInterface.tsx +2 -0
- package/src/components/ConfirmationDetails.tsx +6 -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/components/RewindCommand.tsx +10 -4
- package/src/constants/commands.ts +6 -0
- package/src/contexts/useChat.tsx +146 -7
- package/src/daemon/commands.ts +444 -0
- package/src/daemon/jsonRpcClient.ts +158 -0
- package/src/daemon/socketClient.ts +34 -0
- package/src/hooks/useInputManager.ts +8 -0
- package/src/index.ts +130 -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 +123 -19
- package/src/stdio/protocol.ts +4 -0
- 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
|
};
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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>;
|