wave-code 0.10.4 → 0.11.1

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 (57) hide show
  1. package/dist/acp/agent.d.ts +1 -0
  2. package/dist/acp/agent.d.ts.map +1 -1
  3. package/dist/acp/agent.js +122 -18
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +2 -2
  6. package/dist/components/App.d.ts.map +1 -1
  7. package/dist/components/App.js +4 -4
  8. package/dist/components/BackgroundTaskManager.d.ts.map +1 -1
  9. package/dist/components/BackgroundTaskManager.js +3 -2
  10. package/dist/components/ChatInterface.d.ts.map +1 -1
  11. package/dist/components/ChatInterface.js +2 -1
  12. package/dist/components/ConfirmationDetails.d.ts.map +1 -1
  13. package/dist/components/ConfirmationDetails.js +2 -2
  14. package/dist/components/ConfirmationSelector.d.ts.map +1 -1
  15. package/dist/components/ConfirmationSelector.js +20 -4
  16. package/dist/components/InputBox.d.ts +1 -1
  17. package/dist/components/InputBox.d.ts.map +1 -1
  18. package/dist/components/InputBox.js +1 -2
  19. package/dist/components/MessageList.d.ts +2 -1
  20. package/dist/components/MessageList.d.ts.map +1 -1
  21. package/dist/components/MessageList.js +18 -17
  22. package/dist/constants/commands.d.ts.map +1 -1
  23. package/dist/constants/commands.js +0 -6
  24. package/dist/contexts/useChat.d.ts +3 -2
  25. package/dist/contexts/useChat.d.ts.map +1 -1
  26. package/dist/contexts/useChat.js +32 -28
  27. package/dist/hooks/useInputManager.d.ts.map +1 -1
  28. package/dist/hooks/useInputManager.js +4 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +40 -1
  31. package/dist/managers/inputHandlers.d.ts.map +1 -1
  32. package/dist/managers/inputHandlers.js +7 -8
  33. package/dist/managers/inputReducer.d.ts +2 -3
  34. package/dist/managers/inputReducer.d.ts.map +1 -1
  35. package/dist/managers/inputReducer.js +4 -4
  36. package/dist/print-cli.d.ts.map +1 -1
  37. package/dist/print-cli.js +5 -3
  38. package/dist/types.d.ts +2 -0
  39. package/dist/types.d.ts.map +1 -1
  40. package/package.json +2 -2
  41. package/src/acp/agent.ts +147 -21
  42. package/src/cli.tsx +4 -0
  43. package/src/components/App.tsx +8 -0
  44. package/src/components/BackgroundTaskManager.tsx +17 -1
  45. package/src/components/ChatInterface.tsx +4 -1
  46. package/src/components/ConfirmationDetails.tsx +5 -1
  47. package/src/components/ConfirmationSelector.tsx +18 -4
  48. package/src/components/InputBox.tsx +1 -2
  49. package/src/components/MessageList.tsx +21 -18
  50. package/src/constants/commands.ts +0 -6
  51. package/src/contexts/useChat.tsx +49 -35
  52. package/src/hooks/useInputManager.ts +4 -1
  53. package/src/index.ts +43 -1
  54. package/src/managers/inputHandlers.ts +8 -10
  55. package/src/managers/inputReducer.ts +6 -6
  56. package/src/print-cli.ts +6 -2
  57. package/src/types.ts +2 -0
@@ -25,6 +25,7 @@ import {
25
25
  } from "wave-agent-sdk";
26
26
  import { logger } from "../utils/logger.js";
27
27
  import { displayUsageSummary } from "../utils/usageSummary.js";
28
+ import { expandLongTextPlaceholders } from "../managers/inputHandlers.js";
28
29
 
29
30
  import { BaseAppProps } from "../types.js";
30
31
 
@@ -41,16 +42,17 @@ export interface ChatContextType {
41
42
  queuedMessages: Array<{
42
43
  content: string;
43
44
  images?: Array<{ path: string; mimeType: string }>;
45
+ longTextMap?: Record<string, string>;
44
46
  }>;
45
47
  // AI functionality
46
48
  sessionId: string;
47
49
  sendMessage: (
48
50
  content: string,
49
51
  images?: Array<{ path: string; mimeType: string }>,
52
+ longTextMap?: Record<string, string>,
50
53
  ) => Promise<void>;
51
54
  abortMessage: () => void;
52
55
  latestTotalTokens: number;
53
- clearMessages: () => void;
54
56
  // MCP functionality
55
57
  mcpServers: McpServerStatus[];
56
58
  connectMcpServer: (serverName: string) => Promise<boolean>;
@@ -59,9 +61,12 @@ export interface ChatContextType {
59
61
  backgroundTasks: BackgroundTask[];
60
62
  // Tasks
61
63
  tasks: Task[];
62
- getBackgroundTaskOutput: (
63
- taskId: string,
64
- ) => { stdout: string; stderr: string; status: string } | null;
64
+ getBackgroundTaskOutput: (taskId: string) => {
65
+ stdout: string;
66
+ stderr: string;
67
+ status: string;
68
+ outputPath?: string;
69
+ } | null;
65
70
  stopBackgroundTask: (taskId: string) => boolean;
66
71
  // Slash Command functionality
67
72
  slashCommands: SlashCommand[];
@@ -128,6 +133,8 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
128
133
  permissionMode: initialPermissionMode,
129
134
  pluginDirs,
130
135
  tools,
136
+ allowedTools,
137
+ disallowedTools,
131
138
  workdir,
132
139
  worktreeSession,
133
140
  version,
@@ -149,6 +156,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
149
156
  Array<{
150
157
  content: string;
151
158
  images?: Array<{ path: string; mimeType: string }>;
159
+ longTextMap?: Record<string, string>;
152
160
  }>
153
161
  >([]);
154
162
 
@@ -349,6 +357,8 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
349
357
  stream: false, // 关闭流式模式
350
358
  plugins: pluginDirs?.map((path) => ({ type: "local", path })),
351
359
  tools,
360
+ allowedTools,
361
+ disallowedTools,
352
362
  workdir,
353
363
  worktreeName: worktreeSession?.name,
354
364
  isNewWorktree: worktreeSession?.isNew,
@@ -387,6 +397,8 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
387
397
  showConfirmation,
388
398
  pluginDirs,
389
399
  tools,
400
+ allowedTools,
401
+ disallowedTools,
390
402
  workdir,
391
403
  worktreeSession,
392
404
  model,
@@ -421,6 +433,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
421
433
  async (
422
434
  content: string,
423
435
  images?: Array<{ path: string; mimeType: string }>,
436
+ longTextMap?: Record<string, string>,
424
437
  ) => {
425
438
  // Check if there's content to send (text content or image attachments)
426
439
  const hasTextContent = content.trim();
@@ -429,40 +442,41 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
429
442
  if (!hasTextContent && !hasImageAttachments) return;
430
443
 
431
444
  if (isLoading || isCommandRunning) {
432
- setQueuedMessages((prev) => [...prev, { content, images }]);
445
+ setQueuedMessages((prev) => [
446
+ ...prev,
447
+ { content, images, longTextMap },
448
+ ]);
433
449
  return;
434
450
  }
435
451
 
436
452
  try {
437
- // Handle bash mode - check if it's a bash command (starts with ! and only one line)
438
- if (content.startsWith("!") && !content.includes("\n")) {
439
- const command = content.substring(1).trim();
440
- if (!command) return;
441
-
442
- // In bash mode, don't add user message to UI, directly execute command
443
- // Executing bash command will automatically add assistant message
444
-
445
- // Set command running state
446
- setIsCommandRunning(true);
453
+ const expandedContent = longTextMap
454
+ ? expandLongTextPlaceholders(content, longTextMap)
455
+ : content;
447
456
 
448
- try {
449
- await agentRef.current?.executeBashCommand(command);
450
- } finally {
451
- // Clear command running state
452
- setIsCommandRunning(false);
457
+ // Handle bash mode - check if it's a bash command (starts with ! and only one line)
458
+ if (
459
+ expandedContent.startsWith("!") &&
460
+ !expandedContent.includes("\n") &&
461
+ !hasImageAttachments
462
+ ) {
463
+ const command = expandedContent.substring(1).trim();
464
+ if (command) {
465
+ setIsCommandRunning(true);
466
+ try {
467
+ await agentRef.current?.executeBashCommand(command);
468
+ } finally {
469
+ setIsCommandRunning(false);
470
+ }
471
+ return;
453
472
  }
454
-
455
- return;
456
473
  }
457
474
 
458
- // Handle normal AI message and slash commands
459
- // Slash commands are now handled internally in agent.sendMessage
460
-
461
475
  // Set loading state
462
476
  setIsLoading(true);
463
477
 
464
478
  try {
465
- await agentRef.current?.sendMessage(content, images);
479
+ await agentRef.current?.sendMessage(expandedContent, images);
466
480
  } finally {
467
481
  // Clear loading state
468
482
  setIsLoading(false);
@@ -480,7 +494,11 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
480
494
  if (!isLoading && !isCommandRunning && queuedMessages.length > 0) {
481
495
  const nextMessage = queuedMessages[0];
482
496
  setQueuedMessages((prev) => prev.slice(1));
483
- sendMessage(nextMessage.content, nextMessage.images);
497
+ sendMessage(
498
+ nextMessage.content,
499
+ nextMessage.images,
500
+ nextMessage.longTextMap,
501
+ );
484
502
  }
485
503
  }, [isLoading, isCommandRunning, queuedMessages, sendMessage]);
486
504
 
@@ -490,11 +508,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
490
508
  agentRef.current?.abortMessage();
491
509
  }, []);
492
510
 
493
- const clearMessages = useCallback(() => {
494
- setQueuedMessages([]);
495
- agentRef.current?.clearMessages();
496
- }, []);
497
-
498
511
  // Permission management methods
499
512
  const setPermissionMode = useCallback((mode: PermissionMode) => {
500
513
  setPermissionModeState((prev) => {
@@ -643,8 +656,10 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
643
656
  }
644
657
 
645
658
  // Handle ESC key to cancel confirmation
646
- if (key.escape && isConfirmationVisible) {
647
- handleConfirmationCancel();
659
+ if (key.escape) {
660
+ if (isConfirmationVisible) {
661
+ handleConfirmationCancel();
662
+ }
648
663
  }
649
664
  });
650
665
 
@@ -659,7 +674,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
659
674
  sessionId,
660
675
  sendMessage,
661
676
  abortMessage,
662
- clearMessages,
663
677
  latestTotalTokens,
664
678
  isCompressing,
665
679
  mcpServers,
@@ -70,7 +70,10 @@ export const useInputManager = (
70
70
  /\r/g,
71
71
  "\n",
72
72
  );
73
- dispatch({ type: "COMPRESS_AND_INSERT_TEXT", payload: processedInput });
73
+ dispatch({
74
+ type: "INSERT_TEXT_WITH_PLACEHOLDER",
75
+ payload: processedInput,
76
+ });
74
77
  dispatch({ type: "END_PASTE" });
75
78
  dispatch({ type: "RESET_HISTORY_NAVIGATION" });
76
79
  }, pasteDebounceDelay);
package/src/index.ts CHANGED
@@ -77,6 +77,18 @@ export async function main() {
77
77
  type: "string",
78
78
  global: false,
79
79
  })
80
+ .option("allowed-tools", {
81
+ description:
82
+ "Specify a comma-separated list of tools to always allow (e.g., 'Bash(ls),Read')",
83
+ type: "string",
84
+ global: false,
85
+ })
86
+ .option("disallowed-tools", {
87
+ description:
88
+ "Specify a comma-separated list of tools to always disallow (e.g., 'Bash(rm *),Write')",
89
+ type: "string",
90
+ global: false,
91
+ })
80
92
  .option("model", {
81
93
  description: "Specify the AI model to use",
82
94
  type: "string",
@@ -234,10 +246,34 @@ export async function main() {
234
246
  const parseTools = (tools: string | undefined): string[] | undefined => {
235
247
  if (tools === undefined || tools === "default") return undefined;
236
248
  if (tools === "") return [];
237
- return tools.split(",").map((t) => t.trim());
249
+
250
+ // Improved parsing to handle commas inside parentheses
251
+ const result: string[] = [];
252
+ let current = "";
253
+ let depth = 0;
254
+ for (let i = 0; i < tools.length; i++) {
255
+ const char = tools[i];
256
+ if (char === "(") depth++;
257
+ else if (char === ")") depth--;
258
+
259
+ if (char === "," && depth === 0) {
260
+ result.push(current.trim());
261
+ current = "";
262
+ } else {
263
+ current += char;
264
+ }
265
+ }
266
+ if (current.trim()) {
267
+ result.push(current.trim());
268
+ }
269
+ return result;
238
270
  };
239
271
 
240
272
  const tools = parseTools(argv.tools as string | undefined);
273
+ const allowedTools = parseTools(argv.allowedTools as string | undefined);
274
+ const disallowedTools = parseTools(
275
+ argv.disallowedTools as string | undefined,
276
+ );
241
277
 
242
278
  // Resolve plugin directories to absolute paths before any worktree logic
243
279
  const pluginDirs = (argv.pluginDir as string[] | undefined)?.map((dir) =>
@@ -290,6 +326,8 @@ export async function main() {
290
326
  permissionMode: argv.permissionMode as PermissionMode | undefined,
291
327
  pluginDirs,
292
328
  tools,
329
+ allowedTools,
330
+ disallowedTools,
293
331
  worktreeSession,
294
332
  workdir,
295
333
  version,
@@ -311,6 +349,8 @@ export async function main() {
311
349
  permissionMode: argv.permissionMode as PermissionMode | undefined,
312
350
  pluginDirs,
313
351
  tools,
352
+ allowedTools,
353
+ disallowedTools,
314
354
  worktreeSession,
315
355
  workdir,
316
356
  version,
@@ -325,6 +365,8 @@ export async function main() {
325
365
  permissionMode: argv.permissionMode as PermissionMode | undefined,
326
366
  pluginDirs,
327
367
  tools,
368
+ allowedTools,
369
+ disallowedTools,
328
370
  worktreeSession,
329
371
  workdir,
330
372
  version,
@@ -54,10 +54,6 @@ export const handleSubmit = async (
54
54
  const contentWithPlaceholders = state.inputText
55
55
  .replace(imageRegex, "")
56
56
  .trim();
57
- const cleanContent = expandLongTextPlaceholders(
58
- contentWithPlaceholders,
59
- state.longTextMap,
60
- );
61
57
 
62
58
  PromptHistoryManager.addEntry(
63
59
  contentWithPlaceholders,
@@ -69,8 +65,9 @@ export const handleSubmit = async (
69
65
  });
70
66
 
71
67
  callbacks.onSendMessage?.(
72
- cleanContent,
68
+ contentWithPlaceholders,
73
69
  referencedImages.length > 0 ? referencedImages : undefined,
70
+ state.longTextMap,
74
71
  );
75
72
  dispatch({ type: "CLEAR_INPUT" });
76
73
  dispatch({ type: "RESET_HISTORY_NAVIGATION" });
@@ -322,7 +319,7 @@ export const handleCommandSelect = (
322
319
  if (callbacks.onSendMessage && callbacks.onHasSlashCommand?.(command)) {
323
320
  const fullCommand = `/${command}`;
324
321
  try {
325
- await callbacks.onSendMessage(fullCommand);
322
+ await callbacks.onSendMessage(fullCommand, undefined, {});
326
323
  commandExecuted = true;
327
324
  } catch (error) {
328
325
  console.error("Failed to execute slash command:", error);
@@ -330,10 +327,11 @@ export const handleCommandSelect = (
330
327
  }
331
328
 
332
329
  if (!commandExecuted) {
333
- if (command === "clear") {
334
- callbacks.onClearMessages?.();
335
- } else if (command === "tasks") {
336
- dispatch({ type: "SET_SHOW_BACKGROUND_TASK_MANAGER", payload: true });
330
+ if (command === "tasks") {
331
+ dispatch({
332
+ type: "SET_SHOW_BACKGROUND_TASK_MANAGER",
333
+ payload: true,
334
+ });
337
335
  } else if (command === "mcp") {
338
336
  dispatch({ type: "SET_SHOW_MCP_MANAGER", payload: true });
339
337
  } else if (command === "rewind") {
@@ -37,10 +37,10 @@ export interface InputManagerCallbacks {
37
37
  onSendMessage?: (
38
38
  content: string,
39
39
  images?: Array<{ path: string; mimeType: string }>,
40
+ longTextMap?: Record<string, string>,
40
41
  ) => void | Promise<void>;
41
42
  onHasSlashCommand?: (commandId: string) => boolean;
42
43
  onAbortMessage?: () => void;
43
- onClearMessages?: () => void;
44
44
  onBackgroundCurrentTask?: () => void;
45
45
  onPermissionModeChange?: (mode: PermissionMode) => void;
46
46
  sessionId?: string;
@@ -147,7 +147,7 @@ export type InputAction =
147
147
  | { type: "SET_SHOW_PLUGIN_MANAGER"; payload: boolean }
148
148
  | { type: "SET_PERMISSION_MODE"; payload: PermissionMode }
149
149
  | { type: "SET_SELECTOR_JUST_USED"; payload: boolean }
150
- | { type: "COMPRESS_AND_INSERT_TEXT"; payload: string }
150
+ | { type: "INSERT_TEXT_WITH_PLACEHOLDER"; payload: string }
151
151
  | { type: "CLEAR_LONG_TEXT_MAP" }
152
152
  | { type: "CLEAR_INPUT" }
153
153
  | { type: "START_PASTE"; payload: { buffer: string; cursorPosition: number } }
@@ -342,16 +342,16 @@ export function inputReducer(
342
342
  return { ...state, permissionMode: action.payload };
343
343
  case "SET_SELECTOR_JUST_USED":
344
344
  return { ...state, selectorJustUsed: action.payload };
345
- case "COMPRESS_AND_INSERT_TEXT": {
345
+ case "INSERT_TEXT_WITH_PLACEHOLDER": {
346
346
  let textToInsert = action.payload;
347
347
  let newLongTextCounter = state.longTextCounter;
348
348
  const newLongTextMap = { ...state.longTextMap };
349
349
 
350
350
  if (textToInsert.length > 200) {
351
351
  newLongTextCounter += 1;
352
- const compressedLabel = `[LongText#${newLongTextCounter}]`;
353
- newLongTextMap[compressedLabel] = textToInsert;
354
- textToInsert = compressedLabel;
352
+ const placeholderLabel = `[LongText#${newLongTextCounter}]`;
353
+ newLongTextMap[placeholderLabel] = textToInsert;
354
+ textToInsert = placeholderLabel;
355
355
  }
356
356
 
357
357
  const beforeCursor = state.inputText.substring(0, state.cursorPosition);
package/src/print-cli.ts CHANGED
@@ -41,13 +41,15 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
41
41
  permissionMode,
42
42
  pluginDirs,
43
43
  tools,
44
+ allowedTools,
45
+ disallowedTools,
44
46
  worktreeSession,
45
47
  workdir,
46
48
  model,
47
49
  } = options;
48
50
 
49
51
  if (
50
- (!message || message.trim() === "") &&
52
+ (typeof message !== "string" || message.trim() === "") &&
51
53
  !continueLastSession &&
52
54
  !restoreSessionId
53
55
  ) {
@@ -149,13 +151,15 @@ export async function startPrintCli(options: PrintCliOptions): Promise<void> {
149
151
  permissionMode || (bypassPermissions ? "bypassPermissions" : undefined),
150
152
  plugins: pluginDirs?.map((path) => ({ type: "local", path })),
151
153
  tools,
154
+ allowedTools,
155
+ disallowedTools,
152
156
  workdir,
153
157
  model,
154
158
  // 保持流式模式以获得更好的命令行用户体验
155
159
  });
156
160
 
157
161
  // Send message if provided and not empty
158
- if (message && message.trim() !== "") {
162
+ if (typeof message === "string" && message.trim() !== "") {
159
163
  await agent.sendMessage(message);
160
164
  }
161
165
 
package/src/types.ts CHANGED
@@ -6,6 +6,8 @@ export interface BaseAppProps {
6
6
  permissionMode?: PermissionMode;
7
7
  pluginDirs?: string[];
8
8
  tools?: string[];
9
+ allowedTools?: string[];
10
+ disallowedTools?: string[];
9
11
  worktreeSession?: WorktreeSession;
10
12
  workdir?: string;
11
13
  version?: string;