wave-code 0.10.3 → 0.11.0

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 (56) 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.map +1 -1
  20. package/dist/components/MessageList.js +17 -16
  21. package/dist/constants/commands.d.ts.map +1 -1
  22. package/dist/constants/commands.js +0 -6
  23. package/dist/contexts/useChat.d.ts +3 -2
  24. package/dist/contexts/useChat.d.ts.map +1 -1
  25. package/dist/contexts/useChat.js +26 -28
  26. package/dist/hooks/useInputManager.d.ts.map +1 -1
  27. package/dist/hooks/useInputManager.js +4 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +40 -1
  30. package/dist/managers/inputHandlers.d.ts.map +1 -1
  31. package/dist/managers/inputHandlers.js +7 -8
  32. package/dist/managers/inputReducer.d.ts +2 -3
  33. package/dist/managers/inputReducer.d.ts.map +1 -1
  34. package/dist/managers/inputReducer.js +4 -4
  35. package/dist/print-cli.d.ts.map +1 -1
  36. package/dist/print-cli.js +5 -3
  37. package/dist/types.d.ts +2 -0
  38. package/dist/types.d.ts.map +1 -1
  39. package/package.json +2 -2
  40. package/src/acp/agent.ts +147 -21
  41. package/src/cli.tsx +4 -0
  42. package/src/components/App.tsx +8 -0
  43. package/src/components/BackgroundTaskManager.tsx +17 -1
  44. package/src/components/ChatInterface.tsx +3 -1
  45. package/src/components/ConfirmationDetails.tsx +5 -1
  46. package/src/components/ConfirmationSelector.tsx +18 -4
  47. package/src/components/InputBox.tsx +1 -2
  48. package/src/components/MessageList.tsx +19 -18
  49. package/src/constants/commands.ts +0 -6
  50. package/src/contexts/useChat.tsx +43 -34
  51. package/src/hooks/useInputManager.ts +4 -1
  52. package/src/index.ts +43 -1
  53. package/src/managers/inputHandlers.ts +8 -10
  54. package/src/managers/inputReducer.ts +6 -6
  55. package/src/print-cli.ts +6 -2
  56. 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,36 @@ 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 {
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) {
449
465
  await agentRef.current?.executeBashCommand(command);
450
- } finally {
451
- // Clear command running state
452
- setIsCommandRunning(false);
466
+ return;
453
467
  }
454
-
455
- return;
456
468
  }
457
469
 
458
- // Handle normal AI message and slash commands
459
- // Slash commands are now handled internally in agent.sendMessage
460
-
461
470
  // Set loading state
462
471
  setIsLoading(true);
463
472
 
464
473
  try {
465
- await agentRef.current?.sendMessage(content, images);
474
+ await agentRef.current?.sendMessage(expandedContent, images);
466
475
  } finally {
467
476
  // Clear loading state
468
477
  setIsLoading(false);
@@ -480,7 +489,11 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
480
489
  if (!isLoading && !isCommandRunning && queuedMessages.length > 0) {
481
490
  const nextMessage = queuedMessages[0];
482
491
  setQueuedMessages((prev) => prev.slice(1));
483
- sendMessage(nextMessage.content, nextMessage.images);
492
+ sendMessage(
493
+ nextMessage.content,
494
+ nextMessage.images,
495
+ nextMessage.longTextMap,
496
+ );
484
497
  }
485
498
  }, [isLoading, isCommandRunning, queuedMessages, sendMessage]);
486
499
 
@@ -490,11 +503,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
490
503
  agentRef.current?.abortMessage();
491
504
  }, []);
492
505
 
493
- const clearMessages = useCallback(() => {
494
- setQueuedMessages([]);
495
- agentRef.current?.clearMessages();
496
- }, []);
497
-
498
506
  // Permission management methods
499
507
  const setPermissionMode = useCallback((mode: PermissionMode) => {
500
508
  setPermissionModeState((prev) => {
@@ -643,8 +651,10 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
643
651
  }
644
652
 
645
653
  // Handle ESC key to cancel confirmation
646
- if (key.escape && isConfirmationVisible) {
647
- handleConfirmationCancel();
654
+ if (key.escape) {
655
+ if (isConfirmationVisible) {
656
+ handleConfirmationCancel();
657
+ }
648
658
  }
649
659
  });
650
660
 
@@ -659,7 +669,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
659
669
  sessionId,
660
670
  sendMessage,
661
671
  abortMessage,
662
- clearMessages,
663
672
  latestTotalTokens,
664
673
  isCompressing,
665
674
  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;