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.
Files changed (80) 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 +13 -0
  13. package/dist/components/Confirmation.d.ts.map +1 -0
  14. package/dist/components/Confirmation.js +172 -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/Markdown.d.ts.map +1 -1
  23. package/dist/components/Markdown.js +115 -16
  24. package/dist/components/MemoryDisplay.js +1 -1
  25. package/dist/components/MessageItem.d.ts +1 -2
  26. package/dist/components/MessageItem.d.ts.map +1 -1
  27. package/dist/components/MessageItem.js +3 -3
  28. package/dist/components/MessageList.d.ts.map +1 -1
  29. package/dist/components/MessageList.js +2 -2
  30. package/dist/components/ReasoningDisplay.d.ts +8 -0
  31. package/dist/components/ReasoningDisplay.d.ts.map +1 -0
  32. package/dist/components/ReasoningDisplay.js +10 -0
  33. package/dist/components/ToolResultDisplay.d.ts.map +1 -1
  34. package/dist/components/ToolResultDisplay.js +2 -1
  35. package/dist/contexts/useChat.d.ts +15 -1
  36. package/dist/contexts/useChat.d.ts.map +1 -1
  37. package/dist/contexts/useChat.js +124 -15
  38. package/dist/hooks/useInputManager.d.ts +3 -0
  39. package/dist/hooks/useInputManager.d.ts.map +1 -1
  40. package/dist/hooks/useInputManager.js +17 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +22 -4
  43. package/dist/managers/InputManager.d.ts +8 -0
  44. package/dist/managers/InputManager.d.ts.map +1 -1
  45. package/dist/managers/InputManager.js +33 -2
  46. package/dist/print-cli.d.ts +1 -0
  47. package/dist/print-cli.d.ts.map +1 -1
  48. package/dist/print-cli.js +36 -3
  49. package/dist/utils/toolParameterTransforms.d.ts +23 -0
  50. package/dist/utils/toolParameterTransforms.d.ts.map +1 -0
  51. package/dist/utils/toolParameterTransforms.js +77 -0
  52. package/package.json +10 -8
  53. package/src/cli.tsx +3 -1
  54. package/src/components/App.tsx +7 -3
  55. package/src/components/BashHistorySelector.tsx +26 -3
  56. package/src/components/ChatInterface.tsx +31 -15
  57. package/src/components/Confirmation.tsx +286 -0
  58. package/src/components/DiffDisplay.tsx +300 -0
  59. package/src/components/FileSelector.tsx +2 -4
  60. package/src/components/InputBox.tsx +37 -14
  61. package/src/components/Markdown.tsx +329 -16
  62. package/src/components/MemoryDisplay.tsx +1 -1
  63. package/src/components/MessageItem.tsx +4 -12
  64. package/src/components/MessageList.tsx +0 -2
  65. package/src/components/ReasoningDisplay.tsx +33 -0
  66. package/src/components/ToolResultDisplay.tsx +4 -0
  67. package/src/contexts/useChat.tsx +206 -14
  68. package/src/hooks/useInputManager.ts +19 -0
  69. package/src/index.ts +34 -4
  70. package/src/managers/InputManager.ts +46 -2
  71. package/src/print-cli.ts +42 -2
  72. package/src/utils/toolParameterTransforms.ts +104 -0
  73. package/dist/components/DiffViewer.d.ts +0 -9
  74. package/dist/components/DiffViewer.d.ts.map +0 -1
  75. package/dist/components/DiffViewer.js +0 -221
  76. package/dist/utils/fileSearch.d.ts +0 -20
  77. package/dist/utils/fileSearch.d.ts.map +0 -1
  78. package/dist/utils/fileSearch.js +0 -102
  79. package/src/components/DiffViewer.tsx +0 -323
  80. package/src/utils/fileSearch.ts +0 -133
@@ -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> = ({ children }) => {
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
- // 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
- });
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
- }, [restoreSessionId, continueLastSession]);
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 { 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 ||
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
- onSubagentAssistantContentUpdated: (_subagentId: string, chunk: string) => {
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