wave-code 0.0.6 → 0.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/README.md +1 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/components/App.d.ts +1 -0
- package/dist/components/App.d.ts.map +1 -1
- package/dist/components/App.js +4 -4
- package/dist/components/BashHistorySelector.d.ts.map +1 -1
- package/dist/components/BashHistorySelector.js +17 -3
- package/dist/components/ChatInterface.d.ts.map +1 -1
- package/dist/components/ChatInterface.js +4 -2
- package/dist/components/Confirmation.d.ts +11 -0
- package/dist/components/Confirmation.d.ts.map +1 -0
- package/dist/components/Confirmation.js +148 -0
- package/dist/components/DiffDisplay.d.ts +8 -0
- package/dist/components/DiffDisplay.d.ts.map +1 -0
- package/dist/components/DiffDisplay.js +168 -0
- package/dist/components/FileSelector.d.ts +2 -4
- package/dist/components/FileSelector.d.ts.map +1 -1
- package/dist/components/InputBox.d.ts.map +1 -1
- package/dist/components/InputBox.js +10 -1
- package/dist/components/MemoryDisplay.js +1 -1
- package/dist/components/MessageItem.d.ts +1 -2
- package/dist/components/MessageItem.d.ts.map +1 -1
- package/dist/components/MessageItem.js +3 -3
- package/dist/components/MessageList.d.ts.map +1 -1
- package/dist/components/MessageList.js +2 -2
- package/dist/components/ReasoningDisplay.d.ts +8 -0
- package/dist/components/ReasoningDisplay.d.ts.map +1 -0
- package/dist/components/ReasoningDisplay.js +10 -0
- package/dist/components/ToolResultDisplay.d.ts.map +1 -1
- package/dist/components/ToolResultDisplay.js +2 -1
- package/dist/contexts/useChat.d.ts +13 -1
- package/dist/contexts/useChat.d.ts.map +1 -1
- package/dist/contexts/useChat.js +117 -15
- package/dist/hooks/useInputManager.d.ts +3 -0
- package/dist/hooks/useInputManager.d.ts.map +1 -1
- package/dist/hooks/useInputManager.js +17 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -4
- package/dist/managers/InputManager.d.ts +8 -0
- package/dist/managers/InputManager.d.ts.map +1 -1
- package/dist/managers/InputManager.js +33 -2
- package/dist/print-cli.d.ts +1 -0
- package/dist/print-cli.d.ts.map +1 -1
- package/dist/print-cli.js +36 -3
- package/dist/utils/toolParameterTransforms.d.ts +23 -0
- package/dist/utils/toolParameterTransforms.d.ts.map +1 -0
- package/dist/utils/toolParameterTransforms.js +77 -0
- package/package.json +6 -5
- package/src/cli.tsx +3 -1
- package/src/components/App.tsx +7 -3
- package/src/components/BashHistorySelector.tsx +26 -3
- package/src/components/ChatInterface.tsx +29 -15
- package/src/components/Confirmation.tsx +253 -0
- package/src/components/DiffDisplay.tsx +300 -0
- package/src/components/FileSelector.tsx +2 -4
- package/src/components/InputBox.tsx +37 -14
- package/src/components/MemoryDisplay.tsx +1 -1
- package/src/components/MessageItem.tsx +4 -12
- package/src/components/MessageList.tsx +0 -2
- package/src/components/ReasoningDisplay.tsx +33 -0
- package/src/components/ToolResultDisplay.tsx +4 -0
- package/src/contexts/useChat.tsx +178 -14
- package/src/hooks/useInputManager.ts +19 -0
- package/src/index.ts +34 -4
- package/src/managers/InputManager.ts +46 -2
- package/src/print-cli.ts +42 -2
- package/src/utils/toolParameterTransforms.ts +104 -0
- package/dist/components/DiffViewer.d.ts +0 -9
- package/dist/components/DiffViewer.d.ts.map +0 -1
- package/dist/components/DiffViewer.js +0 -221
- package/dist/utils/fileSearch.d.ts +0 -20
- package/dist/utils/fileSearch.d.ts.map +0 -1
- package/dist/utils/fileSearch.js +0 -102
- package/src/components/DiffViewer.tsx +0 -323
- package/src/utils/fileSearch.ts +0 -133
|
@@ -2,26 +2,24 @@ import React from "react";
|
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import type { Message } from "wave-agent-sdk";
|
|
4
4
|
import { MessageSource } from "wave-agent-sdk";
|
|
5
|
-
import { DiffViewer } from "./DiffViewer.js";
|
|
6
5
|
import { CommandOutputDisplay } from "./CommandOutputDisplay.js";
|
|
7
6
|
import { ToolResultDisplay } from "./ToolResultDisplay.js";
|
|
8
7
|
import { MemoryDisplay } from "./MemoryDisplay.js";
|
|
9
8
|
import { CompressDisplay } from "./CompressDisplay.js";
|
|
10
9
|
import { SubagentBlock } from "./SubagentBlock.js";
|
|
10
|
+
import { ReasoningDisplay } from "./ReasoningDisplay.js";
|
|
11
11
|
import { Markdown } from "./Markdown.js";
|
|
12
12
|
|
|
13
13
|
export interface MessageItemProps {
|
|
14
14
|
message: Message;
|
|
15
15
|
isExpanded: boolean;
|
|
16
16
|
shouldShowHeader: boolean;
|
|
17
|
-
isStatic?: boolean;
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
export const MessageItem = ({
|
|
21
20
|
message,
|
|
22
21
|
isExpanded,
|
|
23
22
|
shouldShowHeader,
|
|
24
|
-
isStatic = true,
|
|
25
23
|
}: MessageItemProps) => {
|
|
26
24
|
if (message.blocks.length === 0) return null;
|
|
27
25
|
return (
|
|
@@ -49,11 +47,7 @@ export const MessageItem = ({
|
|
|
49
47
|
🔗{" "}
|
|
50
48
|
</Text>
|
|
51
49
|
)}
|
|
52
|
-
{
|
|
53
|
-
<Markdown>{block.content}</Markdown>
|
|
54
|
-
) : (
|
|
55
|
-
<Text>{block.content.split("\n").slice(-10).join("\n")}</Text>
|
|
56
|
-
)}
|
|
50
|
+
<Markdown>{block.content}</Markdown>
|
|
57
51
|
</Box>
|
|
58
52
|
)}
|
|
59
53
|
|
|
@@ -63,10 +57,6 @@ export const MessageItem = ({
|
|
|
63
57
|
</Box>
|
|
64
58
|
)}
|
|
65
59
|
|
|
66
|
-
{block.type === "diff" && (
|
|
67
|
-
<DiffViewer block={block} isStatic={isStatic} />
|
|
68
|
-
)}
|
|
69
|
-
|
|
70
60
|
{block.type === "command_output" && (
|
|
71
61
|
<CommandOutputDisplay block={block} isExpanded={isExpanded} />
|
|
72
62
|
)}
|
|
@@ -96,6 +86,8 @@ export const MessageItem = ({
|
|
|
96
86
|
)}
|
|
97
87
|
|
|
98
88
|
{block.type === "subagent" && <SubagentBlock block={block} />}
|
|
89
|
+
|
|
90
|
+
{block.type === "reasoning" && <ReasoningDisplay block={block} />}
|
|
99
91
|
</Box>
|
|
100
92
|
))}
|
|
101
93
|
</Box>
|
|
@@ -75,7 +75,6 @@ export const MessageList = React.memo(
|
|
|
75
75
|
message={message}
|
|
76
76
|
shouldShowHeader={previousMessage?.role !== message.role}
|
|
77
77
|
isExpanded={isExpanded}
|
|
78
|
-
isStatic={true}
|
|
79
78
|
/>
|
|
80
79
|
);
|
|
81
80
|
}}
|
|
@@ -92,7 +91,6 @@ export const MessageList = React.memo(
|
|
|
92
91
|
message={message}
|
|
93
92
|
shouldShowHeader={previousMessage?.role !== message.role}
|
|
94
93
|
isExpanded={isExpanded}
|
|
95
|
-
isStatic={false}
|
|
96
94
|
/>
|
|
97
95
|
</Box>
|
|
98
96
|
);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box } from "ink";
|
|
3
|
+
import type { ReasoningBlock } from "wave-agent-sdk";
|
|
4
|
+
import { Markdown } from "./Markdown.js";
|
|
5
|
+
|
|
6
|
+
interface ReasoningDisplayProps {
|
|
7
|
+
block: ReasoningBlock;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const ReasoningDisplay: React.FC<ReasoningDisplayProps> = ({
|
|
11
|
+
block,
|
|
12
|
+
}) => {
|
|
13
|
+
const { content } = block;
|
|
14
|
+
|
|
15
|
+
if (!content || !content.trim()) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<Box
|
|
21
|
+
borderRight={false}
|
|
22
|
+
borderTop={false}
|
|
23
|
+
borderBottom={false}
|
|
24
|
+
borderStyle="classic"
|
|
25
|
+
borderColor="blue"
|
|
26
|
+
paddingLeft={1}
|
|
27
|
+
>
|
|
28
|
+
<Box flexDirection="column">
|
|
29
|
+
<Markdown>{content}</Markdown>
|
|
30
|
+
</Box>
|
|
31
|
+
</Box>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import type { ToolBlock } from "wave-agent-sdk";
|
|
4
|
+
import { DiffDisplay } from "./DiffDisplay.js";
|
|
4
5
|
|
|
5
6
|
interface ToolResultDisplayProps {
|
|
6
7
|
block: ToolBlock;
|
|
@@ -133,6 +134,9 @@ export const ToolResultDisplay: React.FC<ToolResultDisplayProps> = ({
|
|
|
133
134
|
</Text>
|
|
134
135
|
</Box>
|
|
135
136
|
)}
|
|
137
|
+
|
|
138
|
+
{/* Diff display - handled by DiffDisplay component */}
|
|
139
|
+
<DiffDisplay toolBlock={block} />
|
|
136
140
|
</Box>
|
|
137
141
|
);
|
|
138
142
|
};
|
package/src/contexts/useChat.tsx
CHANGED
|
@@ -13,8 +13,14 @@ import type {
|
|
|
13
13
|
McpServerStatus,
|
|
14
14
|
BackgroundShell,
|
|
15
15
|
SlashCommand,
|
|
16
|
+
PermissionDecision,
|
|
17
|
+
PermissionMode,
|
|
18
|
+
} from "wave-agent-sdk";
|
|
19
|
+
import {
|
|
20
|
+
Agent,
|
|
21
|
+
AgentCallbacks,
|
|
22
|
+
type ToolPermissionContext,
|
|
16
23
|
} from "wave-agent-sdk";
|
|
17
|
-
import { Agent, AgentCallbacks } from "wave-agent-sdk";
|
|
18
24
|
import { logger } from "../utils/logger.js";
|
|
19
25
|
import { displayUsageSummary } from "../utils/usageSummary.js";
|
|
20
26
|
|
|
@@ -52,6 +58,19 @@ export interface ChatContextType {
|
|
|
52
58
|
hasSlashCommand: (commandId: string) => boolean;
|
|
53
59
|
// Subagent messages
|
|
54
60
|
subagentMessages: Record<string, Message[]>;
|
|
61
|
+
// Permission functionality
|
|
62
|
+
permissionMode: PermissionMode;
|
|
63
|
+
setPermissionMode: (mode: PermissionMode) => void;
|
|
64
|
+
// Permission confirmation state
|
|
65
|
+
isConfirmationVisible: boolean;
|
|
66
|
+
confirmingTool?: { name: string; input?: Record<string, unknown> };
|
|
67
|
+
showConfirmation: (
|
|
68
|
+
toolName: string,
|
|
69
|
+
toolInput?: Record<string, unknown>,
|
|
70
|
+
) => Promise<PermissionDecision>;
|
|
71
|
+
hideConfirmation: () => void;
|
|
72
|
+
handleConfirmationDecision: (decision: PermissionDecision) => void;
|
|
73
|
+
handleConfirmationCancel: () => void;
|
|
55
74
|
}
|
|
56
75
|
|
|
57
76
|
const ChatContext = createContext<ChatContextType | null>(null);
|
|
@@ -66,9 +85,13 @@ export const useChat = () => {
|
|
|
66
85
|
|
|
67
86
|
export interface ChatProviderProps {
|
|
68
87
|
children: React.ReactNode;
|
|
88
|
+
bypassPermissions?: boolean;
|
|
69
89
|
}
|
|
70
90
|
|
|
71
|
-
export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
91
|
+
export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
92
|
+
children,
|
|
93
|
+
bypassPermissions,
|
|
94
|
+
}) => {
|
|
72
95
|
const { restoreSessionId, continueLastSession } = useAppConfig();
|
|
73
96
|
|
|
74
97
|
// Message Display State
|
|
@@ -99,20 +122,52 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
|
|
|
99
122
|
Record<string, Message[]>
|
|
100
123
|
>({});
|
|
101
124
|
|
|
125
|
+
// Permission state
|
|
126
|
+
const [permissionMode, setPermissionModeState] =
|
|
127
|
+
useState<PermissionMode>("default");
|
|
128
|
+
|
|
129
|
+
// Confirmation state with queue-based architecture
|
|
130
|
+
const [isConfirmationVisible, setIsConfirmationVisible] = useState(false);
|
|
131
|
+
const [confirmingTool, setConfirmingTool] = useState<
|
|
132
|
+
{ name: string; input?: Record<string, unknown> } | undefined
|
|
133
|
+
>();
|
|
134
|
+
const [confirmationQueue, setConfirmationQueue] = useState<
|
|
135
|
+
Array<{
|
|
136
|
+
toolName: string;
|
|
137
|
+
toolInput?: Record<string, unknown>;
|
|
138
|
+
resolver: (decision: PermissionDecision) => void;
|
|
139
|
+
reject: () => void;
|
|
140
|
+
}>
|
|
141
|
+
>([]);
|
|
142
|
+
const [currentConfirmation, setCurrentConfirmation] = useState<{
|
|
143
|
+
toolName: string;
|
|
144
|
+
toolInput?: Record<string, unknown>;
|
|
145
|
+
resolver: (decision: PermissionDecision) => void;
|
|
146
|
+
reject: () => void;
|
|
147
|
+
} | null>(null);
|
|
148
|
+
|
|
102
149
|
const agentRef = useRef<Agent | null>(null);
|
|
103
150
|
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
151
|
+
// Permission confirmation methods with queue support
|
|
152
|
+
const showConfirmation = useCallback(
|
|
153
|
+
async (
|
|
154
|
+
toolName: string,
|
|
155
|
+
toolInput?: Record<string, unknown>,
|
|
156
|
+
): Promise<PermissionDecision> => {
|
|
157
|
+
return new Promise<PermissionDecision>((resolve, reject) => {
|
|
158
|
+
const queueItem = {
|
|
159
|
+
toolName,
|
|
160
|
+
toolInput,
|
|
161
|
+
resolver: resolve,
|
|
162
|
+
reject,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
setConfirmationQueue((prev) => [...prev, queueItem]);
|
|
166
|
+
// processNextConfirmation will be called via useEffect
|
|
113
167
|
});
|
|
114
|
-
}
|
|
115
|
-
|
|
168
|
+
},
|
|
169
|
+
[],
|
|
170
|
+
);
|
|
116
171
|
|
|
117
172
|
// Initialize AI manager
|
|
118
173
|
useEffect(() => {
|
|
@@ -142,19 +197,46 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
|
|
|
142
197
|
setBackgroundShells([...shells]);
|
|
143
198
|
},
|
|
144
199
|
onSubagentMessagesChange: (subagentId, messages) => {
|
|
200
|
+
logger.debug("onSubagentMessagesChange", subagentId, messages.length);
|
|
145
201
|
setSubagentMessages((prev) => ({
|
|
146
202
|
...prev,
|
|
147
203
|
[subagentId]: [...messages],
|
|
148
204
|
}));
|
|
149
205
|
},
|
|
206
|
+
onPermissionModeChange: (mode) => {
|
|
207
|
+
setPermissionModeState(mode);
|
|
208
|
+
},
|
|
150
209
|
};
|
|
151
210
|
|
|
152
211
|
try {
|
|
212
|
+
// Create the permission callback inside the try block to access showConfirmation
|
|
213
|
+
const permissionCallback = bypassPermissions
|
|
214
|
+
? undefined
|
|
215
|
+
: async (
|
|
216
|
+
context: ToolPermissionContext,
|
|
217
|
+
): Promise<PermissionDecision> => {
|
|
218
|
+
try {
|
|
219
|
+
return await showConfirmation(
|
|
220
|
+
context.toolName,
|
|
221
|
+
context.toolInput,
|
|
222
|
+
);
|
|
223
|
+
} catch {
|
|
224
|
+
// If confirmation was cancelled or failed, deny the operation
|
|
225
|
+
return {
|
|
226
|
+
behavior: "deny",
|
|
227
|
+
message: "Operation cancelled by user",
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
153
232
|
const agent = await Agent.create({
|
|
154
233
|
callbacks,
|
|
155
234
|
restoreSessionId,
|
|
156
235
|
continueLastSession,
|
|
157
236
|
logger,
|
|
237
|
+
permissionMode: bypassPermissions ? "bypassPermissions" : undefined,
|
|
238
|
+
canUseTool: permissionCallback,
|
|
239
|
+
stream: false, // 关闭流式模式
|
|
158
240
|
});
|
|
159
241
|
|
|
160
242
|
agentRef.current = agent;
|
|
@@ -167,6 +249,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
|
|
|
167
249
|
setIsCommandRunning(agent.isCommandRunning);
|
|
168
250
|
setIsCompressing(agent.isCompressing);
|
|
169
251
|
setUserInputHistory(agent.userInputHistory);
|
|
252
|
+
setPermissionModeState(agent.getPermissionMode());
|
|
170
253
|
|
|
171
254
|
// Get initial MCP servers state
|
|
172
255
|
const mcpServers = agent.getMcpServers?.() || [];
|
|
@@ -181,7 +264,12 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
|
|
|
181
264
|
};
|
|
182
265
|
|
|
183
266
|
initializeAgent();
|
|
184
|
-
}, [
|
|
267
|
+
}, [
|
|
268
|
+
restoreSessionId,
|
|
269
|
+
continueLastSession,
|
|
270
|
+
bypassPermissions,
|
|
271
|
+
showConfirmation,
|
|
272
|
+
]);
|
|
185
273
|
|
|
186
274
|
// Cleanup on unmount
|
|
187
275
|
useEffect(() => {
|
|
@@ -278,6 +366,17 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
|
|
|
278
366
|
[],
|
|
279
367
|
);
|
|
280
368
|
|
|
369
|
+
// Permission management methods
|
|
370
|
+
const setPermissionMode = useCallback((mode: PermissionMode) => {
|
|
371
|
+
setPermissionModeState((prev) => {
|
|
372
|
+
if (prev === mode) return prev;
|
|
373
|
+
if (agentRef.current && agentRef.current.getPermissionMode() !== mode) {
|
|
374
|
+
agentRef.current.setPermissionMode(mode);
|
|
375
|
+
}
|
|
376
|
+
return mode;
|
|
377
|
+
});
|
|
378
|
+
}, []);
|
|
379
|
+
|
|
281
380
|
// MCP management methods - delegate to Agent
|
|
282
381
|
const connectMcpServer = useCallback(async (serverName: string) => {
|
|
283
382
|
return (await agentRef.current?.connectMcpServer(serverName)) ?? false;
|
|
@@ -303,6 +402,63 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
|
|
|
303
402
|
return agentRef.current.hasSlashCommand(commandId);
|
|
304
403
|
}, []);
|
|
305
404
|
|
|
405
|
+
// Queue processing helper
|
|
406
|
+
const processNextConfirmation = useCallback(() => {
|
|
407
|
+
if (confirmationQueue.length > 0 && !isConfirmationVisible) {
|
|
408
|
+
const next = confirmationQueue[0];
|
|
409
|
+
setCurrentConfirmation(next);
|
|
410
|
+
setConfirmingTool({ name: next.toolName, input: next.toolInput });
|
|
411
|
+
setIsConfirmationVisible(true);
|
|
412
|
+
setConfirmationQueue((prev) => prev.slice(1));
|
|
413
|
+
}
|
|
414
|
+
}, [confirmationQueue, isConfirmationVisible]);
|
|
415
|
+
|
|
416
|
+
// Process queue when queue changes or confirmation is hidden
|
|
417
|
+
useEffect(() => {
|
|
418
|
+
processNextConfirmation();
|
|
419
|
+
}, [processNextConfirmation]);
|
|
420
|
+
|
|
421
|
+
const hideConfirmation = useCallback(() => {
|
|
422
|
+
setIsConfirmationVisible(false);
|
|
423
|
+
setConfirmingTool(undefined);
|
|
424
|
+
setCurrentConfirmation(null);
|
|
425
|
+
}, []);
|
|
426
|
+
|
|
427
|
+
const handleConfirmationDecision = useCallback(
|
|
428
|
+
(decision: PermissionDecision) => {
|
|
429
|
+
if (currentConfirmation) {
|
|
430
|
+
currentConfirmation.resolver(decision);
|
|
431
|
+
}
|
|
432
|
+
hideConfirmation();
|
|
433
|
+
},
|
|
434
|
+
[currentConfirmation, hideConfirmation],
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
const handleConfirmationCancel = useCallback(() => {
|
|
438
|
+
if (currentConfirmation) {
|
|
439
|
+
currentConfirmation.reject();
|
|
440
|
+
}
|
|
441
|
+
hideConfirmation();
|
|
442
|
+
}, [currentConfirmation, hideConfirmation]);
|
|
443
|
+
|
|
444
|
+
// Listen for Ctrl+O hotkey to toggle collapse/expand state and ESC to cancel confirmation
|
|
445
|
+
useInput((input, key) => {
|
|
446
|
+
if (key.ctrl && input === "o") {
|
|
447
|
+
// Clear terminal screen when expanded state changes
|
|
448
|
+
process.stdout.write("\x1Bc", () => {
|
|
449
|
+
setIsExpanded((prev) => {
|
|
450
|
+
const newExpanded = !prev;
|
|
451
|
+
return newExpanded;
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Handle ESC key to cancel confirmation
|
|
457
|
+
if (key.escape && isConfirmationVisible) {
|
|
458
|
+
handleConfirmationCancel();
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
|
|
306
462
|
const contextValue: ChatContextType = {
|
|
307
463
|
messages,
|
|
308
464
|
isLoading,
|
|
@@ -324,6 +480,14 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
|
|
|
324
480
|
slashCommands,
|
|
325
481
|
hasSlashCommand,
|
|
326
482
|
subagentMessages,
|
|
483
|
+
permissionMode,
|
|
484
|
+
setPermissionMode,
|
|
485
|
+
isConfirmationVisible,
|
|
486
|
+
confirmingTool,
|
|
487
|
+
showConfirmation,
|
|
488
|
+
hideConfirmation,
|
|
489
|
+
handleConfirmationDecision,
|
|
490
|
+
handleConfirmationCancel,
|
|
327
491
|
};
|
|
328
492
|
|
|
329
493
|
return (
|
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
AttachedImage,
|
|
7
7
|
} from "../managers/InputManager.js";
|
|
8
8
|
import { FileItem } from "../components/FileSelector.js";
|
|
9
|
+
import { PermissionMode } from "wave-agent-sdk";
|
|
10
|
+
import { logger } from "../utils/logger.js";
|
|
9
11
|
|
|
10
12
|
export const useInputManager = (
|
|
11
13
|
callbacks: Partial<InputManagerCallbacks> = {},
|
|
@@ -38,6 +40,8 @@ export const useInputManager = (
|
|
|
38
40
|
});
|
|
39
41
|
const [showBashManager, setShowBashManager] = useState(false);
|
|
40
42
|
const [showMcpManager, setShowMcpManager] = useState(false);
|
|
43
|
+
const [permissionMode, setPermissionModeState] =
|
|
44
|
+
useState<PermissionMode>("default");
|
|
41
45
|
const [attachedImages, setAttachedImages] = useState<AttachedImage[]>([]);
|
|
42
46
|
|
|
43
47
|
// Create InputManager on mount and update callbacks when they change
|
|
@@ -45,6 +49,7 @@ export const useInputManager = (
|
|
|
45
49
|
if (!managerRef.current) {
|
|
46
50
|
// Create InputManager on first mount
|
|
47
51
|
const manager = new InputManager({
|
|
52
|
+
logger,
|
|
48
53
|
onInputTextChange: setInputText,
|
|
49
54
|
onCursorPositionChange: setCursorPosition,
|
|
50
55
|
onFileSelectorStateChange: (show, files, query, position) => {
|
|
@@ -65,6 +70,10 @@ export const useInputManager = (
|
|
|
65
70
|
onMcpManagerStateChange: (show) => {
|
|
66
71
|
setShowMcpManager(show);
|
|
67
72
|
},
|
|
73
|
+
onPermissionModeChange: (mode) => {
|
|
74
|
+
setPermissionModeState(mode);
|
|
75
|
+
callbacks.onPermissionModeChange?.(mode);
|
|
76
|
+
},
|
|
68
77
|
onImagesStateChange: setAttachedImages,
|
|
69
78
|
onShowBashManager: () => setShowBashManager(true),
|
|
70
79
|
onShowMcpManager: () => setShowMcpManager(true),
|
|
@@ -76,6 +85,7 @@ export const useInputManager = (
|
|
|
76
85
|
} else {
|
|
77
86
|
// Update callbacks on existing manager
|
|
78
87
|
managerRef.current.updateCallbacks({
|
|
88
|
+
logger,
|
|
79
89
|
onInputTextChange: setInputText,
|
|
80
90
|
onCursorPositionChange: setCursorPosition,
|
|
81
91
|
onFileSelectorStateChange: (show, files, query, position) => {
|
|
@@ -96,6 +106,10 @@ export const useInputManager = (
|
|
|
96
106
|
onMcpManagerStateChange: (show) => {
|
|
97
107
|
setShowMcpManager(show);
|
|
98
108
|
},
|
|
109
|
+
onPermissionModeChange: (mode) => {
|
|
110
|
+
setPermissionModeState(mode);
|
|
111
|
+
callbacks.onPermissionModeChange?.(mode);
|
|
112
|
+
},
|
|
99
113
|
onImagesStateChange: setAttachedImages,
|
|
100
114
|
onShowBashManager: () => setShowBashManager(true),
|
|
101
115
|
onShowMcpManager: () => setShowMcpManager(true),
|
|
@@ -328,6 +342,7 @@ export const useInputManager = (
|
|
|
328
342
|
memoryMessage: memoryTypeSelectorState.message,
|
|
329
343
|
showBashManager,
|
|
330
344
|
showMcpManager,
|
|
345
|
+
permissionMode,
|
|
331
346
|
attachedImages,
|
|
332
347
|
isManagerReady,
|
|
333
348
|
|
|
@@ -384,6 +399,10 @@ export const useInputManager = (
|
|
|
384
399
|
setShowMcpManager: useCallback((show: boolean) => {
|
|
385
400
|
managerRef.current?.setShowMcpManager(show);
|
|
386
401
|
}, []),
|
|
402
|
+
setPermissionMode: useCallback((mode: PermissionMode) => {
|
|
403
|
+
setPermissionModeState(mode);
|
|
404
|
+
managerRef.current?.setPermissionMode(mode);
|
|
405
|
+
}, []),
|
|
387
406
|
|
|
388
407
|
// Image management
|
|
389
408
|
addImage: useCallback((imagePath: string, mimeType: string) => {
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import yargs from "yargs";
|
|
2
2
|
import { hideBin } from "yargs/helpers";
|
|
3
3
|
import { startCli } from "./cli.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
listSessions,
|
|
6
|
+
getSessionFilePath,
|
|
7
|
+
getFirstMessageContent,
|
|
8
|
+
} from "wave-agent-sdk";
|
|
5
9
|
|
|
6
10
|
// Export main function for external use
|
|
7
11
|
export async function main() {
|
|
@@ -29,6 +33,11 @@ export async function main() {
|
|
|
29
33
|
description: "List all available sessions",
|
|
30
34
|
type: "boolean",
|
|
31
35
|
})
|
|
36
|
+
.option("dangerously-skip-permissions", {
|
|
37
|
+
description: "Skip all permission checks (dangerous)",
|
|
38
|
+
type: "boolean",
|
|
39
|
+
default: false,
|
|
40
|
+
})
|
|
32
41
|
.version()
|
|
33
42
|
.alias("v", "version")
|
|
34
43
|
.example("$0", "Start CLI with default settings")
|
|
@@ -57,20 +66,39 @@ export async function main() {
|
|
|
57
66
|
console.log(`Available sessions for: ${currentWorkdir}`);
|
|
58
67
|
console.log("==========================================");
|
|
59
68
|
|
|
60
|
-
|
|
61
|
-
|
|
69
|
+
// Get last 5 sessions
|
|
70
|
+
const lastSessions = sessions.slice(0, 5);
|
|
71
|
+
|
|
72
|
+
for (const session of lastSessions) {
|
|
62
73
|
const lastActiveAt = new Date(session.lastActiveAt).toLocaleString();
|
|
63
74
|
const filePath = await getSessionFilePath(session.id, session.workdir);
|
|
64
75
|
|
|
76
|
+
// Get first message content
|
|
77
|
+
const firstMessageContent = await getFirstMessageContent(
|
|
78
|
+
session.id,
|
|
79
|
+
session.workdir,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Truncate content if too long
|
|
83
|
+
let truncatedContent =
|
|
84
|
+
firstMessageContent || "No first message content";
|
|
85
|
+
if (truncatedContent.length > 30) {
|
|
86
|
+
truncatedContent = truncatedContent.substring(0, 30) + "...";
|
|
87
|
+
}
|
|
88
|
+
|
|
65
89
|
console.log(`ID: ${session.id}`);
|
|
66
90
|
console.log(` Workdir: ${session.workdir}`);
|
|
67
91
|
console.log(` File Path: ${filePath}`);
|
|
68
|
-
console.log(` Started: ${startedAt}`);
|
|
69
92
|
console.log(` Last Active: ${lastActiveAt}`);
|
|
70
93
|
console.log(` Last Message Tokens: ${session.latestTotalTokens}`);
|
|
94
|
+
console.log(` First Message: ${truncatedContent}`);
|
|
71
95
|
console.log("");
|
|
72
96
|
}
|
|
73
97
|
|
|
98
|
+
if (sessions.length > 5) {
|
|
99
|
+
console.log(`... and ${sessions.length - 5} more sessions`);
|
|
100
|
+
}
|
|
101
|
+
|
|
74
102
|
return;
|
|
75
103
|
} catch (error) {
|
|
76
104
|
console.error("Failed to list sessions:", error);
|
|
@@ -86,12 +114,14 @@ export async function main() {
|
|
|
86
114
|
continueLastSession: argv.continue,
|
|
87
115
|
message: argv.print,
|
|
88
116
|
showStats: argv.showStats,
|
|
117
|
+
bypassPermissions: argv.dangerouslySkipPermissions,
|
|
89
118
|
});
|
|
90
119
|
}
|
|
91
120
|
|
|
92
121
|
await startCli({
|
|
93
122
|
restoreSessionId: argv.restore,
|
|
94
123
|
continueLastSession: argv.continue,
|
|
124
|
+
bypassPermissions: argv.dangerouslySkipPermissions,
|
|
95
125
|
});
|
|
96
126
|
}
|
|
97
127
|
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { FileItem } from "../components/FileSelector.js";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
searchFiles as searchFilesUtil,
|
|
4
|
+
PermissionMode,
|
|
5
|
+
Logger,
|
|
6
|
+
} from "wave-agent-sdk";
|
|
3
7
|
import { readClipboardImage } from "../utils/clipboard.js";
|
|
4
8
|
import type { Key } from "ink";
|
|
5
9
|
|
|
@@ -42,6 +46,8 @@ export interface InputManagerCallbacks {
|
|
|
42
46
|
onSaveMemory?: (message: string, type: "project" | "user") => Promise<void>;
|
|
43
47
|
onAbortMessage?: () => void;
|
|
44
48
|
onResetHistoryNavigation?: () => void;
|
|
49
|
+
onPermissionModeChange?: (mode: PermissionMode) => void;
|
|
50
|
+
logger?: Logger;
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
export class InputManager {
|
|
@@ -93,18 +99,26 @@ export class InputManager {
|
|
|
93
99
|
private showBashManager: boolean = false;
|
|
94
100
|
private showMcpManager: boolean = false;
|
|
95
101
|
|
|
102
|
+
// Permission mode state
|
|
103
|
+
private permissionMode: PermissionMode = "default";
|
|
104
|
+
|
|
96
105
|
// Flag to prevent handleInput conflicts when selector selection occurs
|
|
97
106
|
private selectorJustUsed: boolean = false;
|
|
98
107
|
|
|
99
108
|
private callbacks: InputManagerCallbacks;
|
|
109
|
+
private logger?: Logger;
|
|
100
110
|
|
|
101
111
|
constructor(callbacks: InputManagerCallbacks = {}) {
|
|
102
112
|
this.callbacks = callbacks;
|
|
113
|
+
this.logger = callbacks.logger;
|
|
103
114
|
}
|
|
104
115
|
|
|
105
116
|
// Update callbacks
|
|
106
117
|
updateCallbacks(callbacks: Partial<InputManagerCallbacks>) {
|
|
107
118
|
this.callbacks = { ...this.callbacks, ...callbacks };
|
|
119
|
+
if (callbacks.logger) {
|
|
120
|
+
this.logger = callbacks.logger;
|
|
121
|
+
}
|
|
108
122
|
}
|
|
109
123
|
|
|
110
124
|
// Core input methods
|
|
@@ -854,6 +868,29 @@ export class InputManager {
|
|
|
854
868
|
this.callbacks.onMcpManagerStateChange?.(show);
|
|
855
869
|
}
|
|
856
870
|
|
|
871
|
+
// Permission mode methods
|
|
872
|
+
getPermissionMode(): PermissionMode {
|
|
873
|
+
return this.permissionMode;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
setPermissionMode(mode: PermissionMode): void {
|
|
877
|
+
this.permissionMode = mode;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
cyclePermissionMode(): void {
|
|
881
|
+
const modes: PermissionMode[] = ["default", "acceptEdits"];
|
|
882
|
+
const currentIndex = modes.indexOf(this.permissionMode);
|
|
883
|
+
const nextIndex =
|
|
884
|
+
currentIndex === -1 ? 0 : (currentIndex + 1) % modes.length;
|
|
885
|
+
const nextMode = modes[nextIndex];
|
|
886
|
+
this.logger?.debug("Cycling permission mode", {
|
|
887
|
+
from: this.permissionMode,
|
|
888
|
+
to: nextMode,
|
|
889
|
+
});
|
|
890
|
+
this.permissionMode = nextMode;
|
|
891
|
+
this.callbacks.onPermissionModeChange?.(this.permissionMode);
|
|
892
|
+
}
|
|
893
|
+
|
|
857
894
|
// Handle submit logic
|
|
858
895
|
async handleSubmit(
|
|
859
896
|
attachedImages: Array<{ id: number; path: string; mimeType: string }>,
|
|
@@ -907,7 +944,7 @@ export class InputManager {
|
|
|
907
944
|
|
|
908
945
|
// Handle selector input (when any selector is active)
|
|
909
946
|
handleSelectorInput(input: string, key: Key): boolean {
|
|
910
|
-
if (key.backspace || key.delete) {
|
|
947
|
+
if (key.backspace || (key.delete && !this.showBashHistorySelector)) {
|
|
911
948
|
if (this.cursorPosition > 0) {
|
|
912
949
|
this.deleteCharAtCursor((newInput, newCursorPosition) => {
|
|
913
950
|
// Check for special character deletion
|
|
@@ -1088,6 +1125,13 @@ export class InputManager {
|
|
|
1088
1125
|
return true;
|
|
1089
1126
|
}
|
|
1090
1127
|
|
|
1128
|
+
// Handle Shift+Tab for permission mode cycling
|
|
1129
|
+
if (key.tab && key.shift) {
|
|
1130
|
+
this.logger?.debug("Shift+Tab detected, cycling permission mode");
|
|
1131
|
+
this.cyclePermissionMode();
|
|
1132
|
+
return true;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1091
1135
|
// Check if any selector is active
|
|
1092
1136
|
if (
|
|
1093
1137
|
this.showFileSelector ||
|