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.
Files changed (77) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.d.ts +1 -0
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +2 -2
  5. package/dist/components/App.d.ts +1 -0
  6. package/dist/components/App.d.ts.map +1 -1
  7. package/dist/components/App.js +4 -4
  8. package/dist/components/BashHistorySelector.d.ts.map +1 -1
  9. package/dist/components/BashHistorySelector.js +17 -3
  10. package/dist/components/ChatInterface.d.ts.map +1 -1
  11. package/dist/components/ChatInterface.js +4 -2
  12. package/dist/components/Confirmation.d.ts +11 -0
  13. package/dist/components/Confirmation.d.ts.map +1 -0
  14. package/dist/components/Confirmation.js +148 -0
  15. package/dist/components/DiffDisplay.d.ts +8 -0
  16. package/dist/components/DiffDisplay.d.ts.map +1 -0
  17. package/dist/components/DiffDisplay.js +168 -0
  18. package/dist/components/FileSelector.d.ts +2 -4
  19. package/dist/components/FileSelector.d.ts.map +1 -1
  20. package/dist/components/InputBox.d.ts.map +1 -1
  21. package/dist/components/InputBox.js +10 -1
  22. package/dist/components/MemoryDisplay.js +1 -1
  23. package/dist/components/MessageItem.d.ts +1 -2
  24. package/dist/components/MessageItem.d.ts.map +1 -1
  25. package/dist/components/MessageItem.js +3 -3
  26. package/dist/components/MessageList.d.ts.map +1 -1
  27. package/dist/components/MessageList.js +2 -2
  28. package/dist/components/ReasoningDisplay.d.ts +8 -0
  29. package/dist/components/ReasoningDisplay.d.ts.map +1 -0
  30. package/dist/components/ReasoningDisplay.js +10 -0
  31. package/dist/components/ToolResultDisplay.d.ts.map +1 -1
  32. package/dist/components/ToolResultDisplay.js +2 -1
  33. package/dist/contexts/useChat.d.ts +13 -1
  34. package/dist/contexts/useChat.d.ts.map +1 -1
  35. package/dist/contexts/useChat.js +117 -15
  36. package/dist/hooks/useInputManager.d.ts +3 -0
  37. package/dist/hooks/useInputManager.d.ts.map +1 -1
  38. package/dist/hooks/useInputManager.js +17 -0
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +22 -4
  41. package/dist/managers/InputManager.d.ts +8 -0
  42. package/dist/managers/InputManager.d.ts.map +1 -1
  43. package/dist/managers/InputManager.js +33 -2
  44. package/dist/print-cli.d.ts +1 -0
  45. package/dist/print-cli.d.ts.map +1 -1
  46. package/dist/print-cli.js +36 -3
  47. package/dist/utils/toolParameterTransforms.d.ts +23 -0
  48. package/dist/utils/toolParameterTransforms.d.ts.map +1 -0
  49. package/dist/utils/toolParameterTransforms.js +77 -0
  50. package/package.json +6 -5
  51. package/src/cli.tsx +3 -1
  52. package/src/components/App.tsx +7 -3
  53. package/src/components/BashHistorySelector.tsx +26 -3
  54. package/src/components/ChatInterface.tsx +29 -15
  55. package/src/components/Confirmation.tsx +253 -0
  56. package/src/components/DiffDisplay.tsx +300 -0
  57. package/src/components/FileSelector.tsx +2 -4
  58. package/src/components/InputBox.tsx +37 -14
  59. package/src/components/MemoryDisplay.tsx +1 -1
  60. package/src/components/MessageItem.tsx +4 -12
  61. package/src/components/MessageList.tsx +0 -2
  62. package/src/components/ReasoningDisplay.tsx +33 -0
  63. package/src/components/ToolResultDisplay.tsx +4 -0
  64. package/src/contexts/useChat.tsx +178 -14
  65. package/src/hooks/useInputManager.ts +19 -0
  66. package/src/index.ts +34 -4
  67. package/src/managers/InputManager.ts +46 -2
  68. package/src/print-cli.ts +42 -2
  69. package/src/utils/toolParameterTransforms.ts +104 -0
  70. package/dist/components/DiffViewer.d.ts +0 -9
  71. package/dist/components/DiffViewer.d.ts.map +0 -1
  72. package/dist/components/DiffViewer.js +0 -221
  73. package/dist/utils/fileSearch.d.ts +0 -20
  74. package/dist/utils/fileSearch.d.ts.map +0 -1
  75. package/dist/utils/fileSearch.js +0 -102
  76. package/src/components/DiffViewer.tsx +0 -323
  77. 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
- {isStatic ? (
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
  };
@@ -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> = ({ children }) => {
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
- // Listen for Ctrl+O hotkey to toggle collapse/expand state
105
- useInput((input, key) => {
106
- if (key.ctrl && input === "o") {
107
- // Clear terminal screen when expanded state changes
108
- process.stdout.write("\x1Bc", () => {
109
- setIsExpanded((prev) => {
110
- const newExpanded = !prev;
111
- return newExpanded;
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
- }, [restoreSessionId, continueLastSession]);
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 { listSessions, getSessionFilePath } from "wave-agent-sdk";
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
- for (const session of sessions) {
61
- const startedAt = new Date(session.startedAt).toLocaleString();
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 { searchFiles as searchFilesUtil } from "../utils/fileSearch.js";
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 ||