wave-code 0.0.6 β 0.0.10
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 +13 -0
- package/dist/components/Confirmation.d.ts.map +1 -0
- package/dist/components/Confirmation.js +172 -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/Markdown.d.ts.map +1 -1
- package/dist/components/Markdown.js +115 -16
- 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 +15 -1
- package/dist/contexts/useChat.d.ts.map +1 -1
- package/dist/contexts/useChat.js +124 -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 +10 -8
- 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 +31 -15
- package/src/components/Confirmation.tsx +286 -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/Markdown.tsx +329 -16
- 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 +206 -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
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,26 @@ 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?: {
|
|
67
|
+
name: string;
|
|
68
|
+
input?: Record<string, unknown>;
|
|
69
|
+
suggestedPrefix?: string;
|
|
70
|
+
hidePersistentOption?: boolean;
|
|
71
|
+
};
|
|
72
|
+
showConfirmation: (
|
|
73
|
+
toolName: string,
|
|
74
|
+
toolInput?: Record<string, unknown>,
|
|
75
|
+
suggestedPrefix?: string,
|
|
76
|
+
hidePersistentOption?: boolean,
|
|
77
|
+
) => Promise<PermissionDecision>;
|
|
78
|
+
hideConfirmation: () => void;
|
|
79
|
+
handleConfirmationDecision: (decision: PermissionDecision) => void;
|
|
80
|
+
handleConfirmationCancel: () => void;
|
|
55
81
|
}
|
|
56
82
|
|
|
57
83
|
const ChatContext = createContext<ChatContextType | null>(null);
|
|
@@ -66,9 +92,13 @@ export const useChat = () => {
|
|
|
66
92
|
|
|
67
93
|
export interface ChatProviderProps {
|
|
68
94
|
children: React.ReactNode;
|
|
95
|
+
bypassPermissions?: boolean;
|
|
69
96
|
}
|
|
70
97
|
|
|
71
|
-
export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
98
|
+
export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
99
|
+
children,
|
|
100
|
+
bypassPermissions,
|
|
101
|
+
}) => {
|
|
72
102
|
const { restoreSessionId, continueLastSession } = useAppConfig();
|
|
73
103
|
|
|
74
104
|
// Message Display State
|
|
@@ -99,20 +129,66 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
|
|
|
99
129
|
Record<string, Message[]>
|
|
100
130
|
>({});
|
|
101
131
|
|
|
132
|
+
// Permission state
|
|
133
|
+
const [permissionMode, setPermissionModeState] =
|
|
134
|
+
useState<PermissionMode>("default");
|
|
135
|
+
|
|
136
|
+
// Confirmation state with queue-based architecture
|
|
137
|
+
const [isConfirmationVisible, setIsConfirmationVisible] = useState(false);
|
|
138
|
+
const [confirmingTool, setConfirmingTool] = useState<
|
|
139
|
+
| {
|
|
140
|
+
name: string;
|
|
141
|
+
input?: Record<string, unknown>;
|
|
142
|
+
suggestedPrefix?: string;
|
|
143
|
+
hidePersistentOption?: boolean;
|
|
144
|
+
}
|
|
145
|
+
| undefined
|
|
146
|
+
>();
|
|
147
|
+
const [confirmationQueue, setConfirmationQueue] = useState<
|
|
148
|
+
Array<{
|
|
149
|
+
toolName: string;
|
|
150
|
+
toolInput?: Record<string, unknown>;
|
|
151
|
+
suggestedPrefix?: string;
|
|
152
|
+
hidePersistentOption?: boolean;
|
|
153
|
+
resolver: (decision: PermissionDecision) => void;
|
|
154
|
+
reject: () => void;
|
|
155
|
+
}>
|
|
156
|
+
>([]);
|
|
157
|
+
const [currentConfirmation, setCurrentConfirmation] = useState<{
|
|
158
|
+
toolName: string;
|
|
159
|
+
toolInput?: Record<string, unknown>;
|
|
160
|
+
suggestedPrefix?: string;
|
|
161
|
+
hidePersistentOption?: boolean;
|
|
162
|
+
resolver: (decision: PermissionDecision) => void;
|
|
163
|
+
reject: () => void;
|
|
164
|
+
} | null>(null);
|
|
165
|
+
|
|
102
166
|
const agentRef = useRef<Agent | null>(null);
|
|
103
167
|
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
168
|
+
// Permission confirmation methods with queue support
|
|
169
|
+
const showConfirmation = useCallback(
|
|
170
|
+
async (
|
|
171
|
+
toolName: string,
|
|
172
|
+
toolInput?: Record<string, unknown>,
|
|
173
|
+
suggestedPrefix?: string,
|
|
174
|
+
hidePersistentOption?: boolean,
|
|
175
|
+
): Promise<PermissionDecision> => {
|
|
176
|
+
return new Promise<PermissionDecision>((resolve, reject) => {
|
|
177
|
+
const queueItem = {
|
|
178
|
+
toolName,
|
|
179
|
+
toolInput,
|
|
180
|
+
suggestedPrefix,
|
|
181
|
+
hidePersistentOption,
|
|
182
|
+
resolver: resolve,
|
|
183
|
+
reject,
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
setConfirmationQueue((prev) => [...prev, queueItem]);
|
|
187
|
+
// processNextConfirmation will be called via useEffect
|
|
113
188
|
});
|
|
114
|
-
}
|
|
115
|
-
|
|
189
|
+
},
|
|
190
|
+
[],
|
|
191
|
+
);
|
|
116
192
|
|
|
117
193
|
// Initialize AI manager
|
|
118
194
|
useEffect(() => {
|
|
@@ -142,19 +218,48 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
|
|
|
142
218
|
setBackgroundShells([...shells]);
|
|
143
219
|
},
|
|
144
220
|
onSubagentMessagesChange: (subagentId, messages) => {
|
|
221
|
+
logger.debug("onSubagentMessagesChange", subagentId, messages.length);
|
|
145
222
|
setSubagentMessages((prev) => ({
|
|
146
223
|
...prev,
|
|
147
224
|
[subagentId]: [...messages],
|
|
148
225
|
}));
|
|
149
226
|
},
|
|
227
|
+
onPermissionModeChange: (mode) => {
|
|
228
|
+
setPermissionModeState(mode);
|
|
229
|
+
},
|
|
150
230
|
};
|
|
151
231
|
|
|
152
232
|
try {
|
|
233
|
+
// Create the permission callback inside the try block to access showConfirmation
|
|
234
|
+
const permissionCallback = bypassPermissions
|
|
235
|
+
? undefined
|
|
236
|
+
: async (
|
|
237
|
+
context: ToolPermissionContext,
|
|
238
|
+
): Promise<PermissionDecision> => {
|
|
239
|
+
try {
|
|
240
|
+
return await showConfirmation(
|
|
241
|
+
context.toolName,
|
|
242
|
+
context.toolInput,
|
|
243
|
+
context.suggestedPrefix,
|
|
244
|
+
context.hidePersistentOption,
|
|
245
|
+
);
|
|
246
|
+
} catch {
|
|
247
|
+
// If confirmation was cancelled or failed, deny the operation
|
|
248
|
+
return {
|
|
249
|
+
behavior: "deny",
|
|
250
|
+
message: "Operation cancelled by user",
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
153
255
|
const agent = await Agent.create({
|
|
154
256
|
callbacks,
|
|
155
257
|
restoreSessionId,
|
|
156
258
|
continueLastSession,
|
|
157
259
|
logger,
|
|
260
|
+
permissionMode: bypassPermissions ? "bypassPermissions" : undefined,
|
|
261
|
+
canUseTool: permissionCallback,
|
|
262
|
+
stream: false, // ε
³ιζ΅εΌζ¨‘εΌ
|
|
158
263
|
});
|
|
159
264
|
|
|
160
265
|
agentRef.current = agent;
|
|
@@ -167,6 +272,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
|
|
|
167
272
|
setIsCommandRunning(agent.isCommandRunning);
|
|
168
273
|
setIsCompressing(agent.isCompressing);
|
|
169
274
|
setUserInputHistory(agent.userInputHistory);
|
|
275
|
+
setPermissionModeState(agent.getPermissionMode());
|
|
170
276
|
|
|
171
277
|
// Get initial MCP servers state
|
|
172
278
|
const mcpServers = agent.getMcpServers?.() || [];
|
|
@@ -181,7 +287,12 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
|
|
|
181
287
|
};
|
|
182
288
|
|
|
183
289
|
initializeAgent();
|
|
184
|
-
}, [
|
|
290
|
+
}, [
|
|
291
|
+
restoreSessionId,
|
|
292
|
+
continueLastSession,
|
|
293
|
+
bypassPermissions,
|
|
294
|
+
showConfirmation,
|
|
295
|
+
]);
|
|
185
296
|
|
|
186
297
|
// Cleanup on unmount
|
|
187
298
|
useEffect(() => {
|
|
@@ -278,6 +389,17 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
|
|
|
278
389
|
[],
|
|
279
390
|
);
|
|
280
391
|
|
|
392
|
+
// Permission management methods
|
|
393
|
+
const setPermissionMode = useCallback((mode: PermissionMode) => {
|
|
394
|
+
setPermissionModeState((prev) => {
|
|
395
|
+
if (prev === mode) return prev;
|
|
396
|
+
if (agentRef.current && agentRef.current.getPermissionMode() !== mode) {
|
|
397
|
+
agentRef.current.setPermissionMode(mode);
|
|
398
|
+
}
|
|
399
|
+
return mode;
|
|
400
|
+
});
|
|
401
|
+
}, []);
|
|
402
|
+
|
|
281
403
|
// MCP management methods - delegate to Agent
|
|
282
404
|
const connectMcpServer = useCallback(async (serverName: string) => {
|
|
283
405
|
return (await agentRef.current?.connectMcpServer(serverName)) ?? false;
|
|
@@ -303,6 +425,68 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
|
|
|
303
425
|
return agentRef.current.hasSlashCommand(commandId);
|
|
304
426
|
}, []);
|
|
305
427
|
|
|
428
|
+
// Queue processing helper
|
|
429
|
+
const processNextConfirmation = useCallback(() => {
|
|
430
|
+
if (confirmationQueue.length > 0 && !isConfirmationVisible) {
|
|
431
|
+
const next = confirmationQueue[0];
|
|
432
|
+
setCurrentConfirmation(next);
|
|
433
|
+
setConfirmingTool({
|
|
434
|
+
name: next.toolName,
|
|
435
|
+
input: next.toolInput,
|
|
436
|
+
suggestedPrefix: next.suggestedPrefix,
|
|
437
|
+
hidePersistentOption: next.hidePersistentOption,
|
|
438
|
+
});
|
|
439
|
+
setIsConfirmationVisible(true);
|
|
440
|
+
setConfirmationQueue((prev) => prev.slice(1));
|
|
441
|
+
}
|
|
442
|
+
}, [confirmationQueue, isConfirmationVisible]);
|
|
443
|
+
|
|
444
|
+
// Process queue when queue changes or confirmation is hidden
|
|
445
|
+
useEffect(() => {
|
|
446
|
+
processNextConfirmation();
|
|
447
|
+
}, [processNextConfirmation]);
|
|
448
|
+
|
|
449
|
+
const hideConfirmation = useCallback(() => {
|
|
450
|
+
setIsConfirmationVisible(false);
|
|
451
|
+
setConfirmingTool(undefined);
|
|
452
|
+
setCurrentConfirmation(null);
|
|
453
|
+
}, []);
|
|
454
|
+
|
|
455
|
+
const handleConfirmationDecision = useCallback(
|
|
456
|
+
(decision: PermissionDecision) => {
|
|
457
|
+
if (currentConfirmation) {
|
|
458
|
+
currentConfirmation.resolver(decision);
|
|
459
|
+
}
|
|
460
|
+
hideConfirmation();
|
|
461
|
+
},
|
|
462
|
+
[currentConfirmation, hideConfirmation],
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
const handleConfirmationCancel = useCallback(() => {
|
|
466
|
+
if (currentConfirmation) {
|
|
467
|
+
currentConfirmation.reject();
|
|
468
|
+
}
|
|
469
|
+
hideConfirmation();
|
|
470
|
+
}, [currentConfirmation, hideConfirmation]);
|
|
471
|
+
|
|
472
|
+
// Listen for Ctrl+O hotkey to toggle collapse/expand state and ESC to cancel confirmation
|
|
473
|
+
useInput((input, key) => {
|
|
474
|
+
if (key.ctrl && input === "o") {
|
|
475
|
+
// Clear terminal screen when expanded state changes
|
|
476
|
+
process.stdout.write("\x1Bc", () => {
|
|
477
|
+
setIsExpanded((prev) => {
|
|
478
|
+
const newExpanded = !prev;
|
|
479
|
+
return newExpanded;
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Handle ESC key to cancel confirmation
|
|
485
|
+
if (key.escape && isConfirmationVisible) {
|
|
486
|
+
handleConfirmationCancel();
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
|
|
306
490
|
const contextValue: ChatContextType = {
|
|
307
491
|
messages,
|
|
308
492
|
isLoading,
|
|
@@ -324,6 +508,14 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
|
|
|
324
508
|
slashCommands,
|
|
325
509
|
hasSlashCommand,
|
|
326
510
|
subagentMessages,
|
|
511
|
+
permissionMode,
|
|
512
|
+
setPermissionMode,
|
|
513
|
+
isConfirmationVisible,
|
|
514
|
+
confirmingTool,
|
|
515
|
+
showConfirmation,
|
|
516
|
+
hideConfirmation,
|
|
517
|
+
handleConfirmationDecision,
|
|
518
|
+
handleConfirmationCancel,
|
|
327
519
|
};
|
|
328
520
|
|
|
329
521
|
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 ||
|
package/src/print-cli.ts
CHANGED
|
@@ -6,6 +6,7 @@ export interface PrintCliOptions {
|
|
|
6
6
|
continueLastSession?: boolean;
|
|
7
7
|
message?: string;
|
|
8
8
|
showStats?: boolean;
|
|
9
|
+
bypassPermissions?: boolean;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
function displayTimingInfo(startTime: Date, showStats: boolean): void {
|
|
@@ -29,6 +30,7 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
|
|
|
29
30
|
continueLastSession,
|
|
30
31
|
message,
|
|
31
32
|
showStats = false,
|
|
33
|
+
bypassPermissions,
|
|
32
34
|
} = options;
|
|
33
35
|
|
|
34
36
|
if (
|
|
@@ -43,14 +45,31 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
|
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
let agent: Agent;
|
|
48
|
+
let isReasoning = false;
|
|
49
|
+
let isContent = false;
|
|
50
|
+
const subagentReasoningStates = new Map<string, boolean>();
|
|
51
|
+
const subagentContentStates = new Map<string, boolean>();
|
|
46
52
|
|
|
47
53
|
// Setup callbacks for agent
|
|
48
54
|
const callbacks: AgentCallbacks = {
|
|
49
55
|
onAssistantMessageAdded: () => {
|
|
56
|
+
isReasoning = false;
|
|
57
|
+
isContent = false;
|
|
50
58
|
// Assistant message started - no content to output yet
|
|
51
59
|
process.stdout.write("\n");
|
|
52
60
|
},
|
|
61
|
+
onAssistantReasoningUpdated: (chunk: string) => {
|
|
62
|
+
if (!isReasoning) {
|
|
63
|
+
process.stdout.write("π Reasoning:\n");
|
|
64
|
+
isReasoning = true;
|
|
65
|
+
}
|
|
66
|
+
process.stdout.write(chunk);
|
|
67
|
+
},
|
|
53
68
|
onAssistantContentUpdated: (chunk: string) => {
|
|
69
|
+
if (isReasoning && !isContent) {
|
|
70
|
+
process.stdout.write("\n\nπ Response:\n");
|
|
71
|
+
isContent = true;
|
|
72
|
+
}
|
|
54
73
|
// FR-001: Stream content updates for real-time display - output only the new chunk
|
|
55
74
|
process.stdout.write(chunk);
|
|
56
75
|
},
|
|
@@ -87,11 +106,30 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
|
|
|
87
106
|
process.stdout.write(` ${statusIcon} Subagent status: ${status}\n`);
|
|
88
107
|
},
|
|
89
108
|
// Subagent message callbacks
|
|
90
|
-
onSubagentAssistantMessageAdded: () => {
|
|
109
|
+
onSubagentAssistantMessageAdded: (subagentId: string) => {
|
|
110
|
+
subagentReasoningStates.set(subagentId, false);
|
|
111
|
+
subagentContentStates.set(subagentId, false);
|
|
91
112
|
// Subagent assistant message started - add indentation
|
|
92
113
|
process.stdout.write("\n ");
|
|
93
114
|
},
|
|
94
|
-
|
|
115
|
+
onSubagentAssistantReasoningUpdated: (
|
|
116
|
+
subagentId: string,
|
|
117
|
+
chunk: string,
|
|
118
|
+
) => {
|
|
119
|
+
if (!subagentReasoningStates.get(subagentId)) {
|
|
120
|
+
process.stdout.write("π Reasoning: ");
|
|
121
|
+
subagentReasoningStates.set(subagentId, true);
|
|
122
|
+
}
|
|
123
|
+
process.stdout.write(chunk);
|
|
124
|
+
},
|
|
125
|
+
onSubagentAssistantContentUpdated: (subagentId: string, chunk: string) => {
|
|
126
|
+
if (
|
|
127
|
+
subagentReasoningStates.get(subagentId) &&
|
|
128
|
+
!subagentContentStates.get(subagentId)
|
|
129
|
+
) {
|
|
130
|
+
process.stdout.write("\n π Response: ");
|
|
131
|
+
subagentContentStates.set(subagentId, true);
|
|
132
|
+
}
|
|
95
133
|
// Stream subagent content with indentation - output only the new chunk
|
|
96
134
|
process.stdout.write(chunk);
|
|
97
135
|
},
|
|
@@ -112,6 +150,8 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
|
|
|
112
150
|
callbacks,
|
|
113
151
|
restoreSessionId,
|
|
114
152
|
continueLastSession,
|
|
153
|
+
permissionMode: bypassPermissions ? "bypassPermissions" : undefined,
|
|
154
|
+
// δΏζζ΅εΌζ¨‘εΌδ»₯θ·εΎζ΄ε₯½ηε½δ»€θ‘η¨ζ·δ½ιͺ
|
|
115
155
|
});
|
|
116
156
|
|
|
117
157
|
// Send message if provided and not empty
|