wave-agent-sdk 0.9.7 → 0.10.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 (86) hide show
  1. package/dist/core/session.d.ts +1 -1
  2. package/dist/core/session.d.ts.map +1 -1
  3. package/dist/core/session.js +1 -1
  4. package/dist/index.d.ts +0 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +0 -1
  7. package/dist/managers/aiManager.d.ts.map +1 -1
  8. package/dist/managers/aiManager.js +1 -0
  9. package/dist/managers/backgroundTaskManager.d.ts.map +1 -1
  10. package/dist/managers/backgroundTaskManager.js +59 -6
  11. package/dist/managers/messageManager.d.ts +1 -6
  12. package/dist/managers/messageManager.d.ts.map +1 -1
  13. package/dist/managers/messageManager.js +0 -6
  14. package/dist/managers/permissionManager.d.ts +1 -1
  15. package/dist/managers/permissionManager.d.ts.map +1 -1
  16. package/dist/managers/permissionManager.js +17 -9
  17. package/dist/managers/skillManager.d.ts.map +1 -1
  18. package/dist/managers/skillManager.js +13 -1
  19. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  20. package/dist/managers/slashCommandManager.js +0 -1
  21. package/dist/managers/subagentManager.d.ts +2 -0
  22. package/dist/managers/subagentManager.d.ts.map +1 -1
  23. package/dist/managers/subagentManager.js +22 -0
  24. package/dist/managers/toolManager.d.ts.map +1 -1
  25. package/dist/managers/toolManager.js +1 -0
  26. package/dist/services/session.d.ts +7 -0
  27. package/dist/services/session.d.ts.map +1 -1
  28. package/dist/services/session.js +33 -0
  29. package/dist/tools/agentTool.d.ts.map +1 -1
  30. package/dist/tools/agentTool.js +7 -3
  31. package/dist/tools/askUserQuestion.d.ts.map +1 -1
  32. package/dist/tools/askUserQuestion.js +8 -1
  33. package/dist/tools/bashTool.d.ts.map +1 -1
  34. package/dist/tools/bashTool.js +7 -3
  35. package/dist/tools/editTool.d.ts.map +1 -1
  36. package/dist/tools/editTool.js +1 -1
  37. package/dist/tools/exitPlanMode.d.ts.map +1 -1
  38. package/dist/tools/exitPlanMode.js +1 -1
  39. package/dist/tools/globTool.d.ts.map +1 -1
  40. package/dist/tools/globTool.js +9 -48
  41. package/dist/tools/grepTool.d.ts.map +1 -1
  42. package/dist/tools/grepTool.js +0 -6
  43. package/dist/tools/types.d.ts +2 -0
  44. package/dist/tools/types.d.ts.map +1 -1
  45. package/dist/tools/writeTool.d.ts.map +1 -1
  46. package/dist/tools/writeTool.js +1 -1
  47. package/dist/types/permissions.d.ts +2 -0
  48. package/dist/types/permissions.d.ts.map +1 -1
  49. package/dist/types/processes.d.ts +4 -0
  50. package/dist/types/processes.d.ts.map +1 -1
  51. package/dist/utils/bashParser.d.ts.map +1 -1
  52. package/dist/utils/bashParser.js +50 -0
  53. package/dist/utils/fileSearch.d.ts.map +1 -1
  54. package/dist/utils/fileSearch.js +0 -5
  55. package/dist/utils/openaiClient.d.ts.map +1 -1
  56. package/dist/utils/openaiClient.js +24 -7
  57. package/package.json +1 -1
  58. package/src/core/session.ts +1 -0
  59. package/src/index.ts +0 -1
  60. package/src/managers/aiManager.ts +1 -0
  61. package/src/managers/backgroundTaskManager.ts +62 -6
  62. package/src/managers/messageManager.ts +1 -9
  63. package/src/managers/permissionManager.ts +21 -8
  64. package/src/managers/skillManager.ts +11 -1
  65. package/src/managers/slashCommandManager.ts +0 -1
  66. package/src/managers/subagentManager.ts +31 -0
  67. package/src/managers/toolManager.ts +1 -0
  68. package/src/services/session.ts +41 -0
  69. package/src/tools/agentTool.ts +9 -3
  70. package/src/tools/askUserQuestion.ts +10 -0
  71. package/src/tools/bashTool.ts +7 -2
  72. package/src/tools/editTool.ts +1 -0
  73. package/src/tools/exitPlanMode.ts +1 -0
  74. package/src/tools/globTool.ts +9 -61
  75. package/src/tools/grepTool.ts +0 -7
  76. package/src/tools/types.ts +2 -0
  77. package/src/tools/writeTool.ts +1 -0
  78. package/src/types/permissions.ts +2 -0
  79. package/src/types/processes.ts +4 -0
  80. package/src/utils/bashParser.ts +54 -0
  81. package/src/utils/fileSearch.ts +0 -5
  82. package/src/utils/openaiClient.ts +23 -7
  83. package/dist/utils/fileFilter.d.ts +0 -15
  84. package/dist/utils/fileFilter.d.ts.map +0 -1
  85. package/dist/utils/fileFilter.js +0 -35
  86. package/src/utils/fileFilter.ts +0 -39
@@ -1,4 +1,7 @@
1
1
  import { spawn, type ChildProcess } from "child_process";
2
+ import * as os from "os";
3
+ import * as fs from "fs";
4
+ import * as path from "path";
2
5
  import { BackgroundTask, BackgroundShell } from "../types/processes.js";
3
6
  import { stripAnsiColors } from "../utils/stringUtils.js";
4
7
  import { logger } from "../utils/globalLogger.js";
@@ -64,6 +67,10 @@ export class BackgroundTaskManager {
64
67
  },
65
68
  });
66
69
 
70
+ // Create log file
71
+ const logPath = path.join(os.tmpdir(), `wave-task-${id}.log`);
72
+ const logStream = fs.createWriteStream(logPath, { flags: "a" });
73
+
67
74
  const shell: BackgroundShell = {
68
75
  id,
69
76
  type: "shell",
@@ -73,8 +80,10 @@ export class BackgroundTaskManager {
73
80
  status: "running",
74
81
  stdout: "",
75
82
  stderr: "",
83
+ outputPath: logPath,
76
84
  onStop: () => {
77
85
  try {
86
+ logStream.end();
78
87
  if (child.pid) {
79
88
  process.kill(-child.pid, "SIGTERM");
80
89
  setTimeout(() => {
@@ -109,12 +118,20 @@ export class BackgroundTaskManager {
109
118
  }
110
119
 
111
120
  const onStdout = (data: Buffer | string) => {
112
- shell.stdout += stripAnsiColors(data.toString());
121
+ const stripped = stripAnsiColors(data.toString());
122
+ shell.stdout += stripped;
123
+ if (logStream.writable) {
124
+ logStream.write(stripped);
125
+ }
113
126
  this.notifyTasksChange();
114
127
  };
115
128
 
116
129
  const onStderr = (data: Buffer | string) => {
117
- shell.stderr += stripAnsiColors(data.toString());
130
+ const stripped = stripAnsiColors(data.toString());
131
+ shell.stderr += stripped;
132
+ if (logStream.writable) {
133
+ logStream.write(stripped);
134
+ }
118
135
  this.notifyTasksChange();
119
136
  };
120
137
 
@@ -122,6 +139,9 @@ export class BackgroundTaskManager {
122
139
  if (timeoutHandle) {
123
140
  clearTimeout(timeoutHandle);
124
141
  }
142
+ if (logStream.writable) {
143
+ logStream.end();
144
+ }
125
145
  shell.status = code === 0 ? "completed" : "failed";
126
146
  shell.exitCode = code ?? 0;
127
147
  shell.endTime = Date.now();
@@ -133,8 +153,13 @@ export class BackgroundTaskManager {
133
153
  if (timeoutHandle) {
134
154
  clearTimeout(timeoutHandle);
135
155
  }
156
+ const stripped = `\nProcess error: ${stripAnsiColors(error.message)}`;
136
157
  shell.status = "failed";
137
- shell.stderr += `\nProcess error: ${stripAnsiColors(error.message)}`;
158
+ shell.stderr += stripped;
159
+ if (logStream.writable) {
160
+ logStream.write(stripped);
161
+ logStream.end();
162
+ }
138
163
  shell.exitCode = 1;
139
164
  shell.endTime = Date.now();
140
165
  shell.runtime = shell.endTime - startTime;
@@ -154,6 +179,7 @@ export class BackgroundTaskManager {
154
179
  if (timeoutHandle) {
155
180
  clearTimeout(timeoutHandle);
156
181
  }
182
+ logStream.end();
157
183
  this.tasks.delete(id);
158
184
  this.notifyTasksChange();
159
185
  };
@@ -170,6 +196,18 @@ export class BackgroundTaskManager {
170
196
  const id = this.generateId();
171
197
  const startTime = Date.now();
172
198
 
199
+ // Create log file
200
+ const logPath = path.join(os.tmpdir(), `wave-task-${id}.log`);
201
+ const logStream = fs.createWriteStream(logPath, { flags: "a" });
202
+
203
+ // Write initial output to log file
204
+ if (initialStdout) {
205
+ logStream.write(stripAnsiColors(initialStdout));
206
+ }
207
+ if (initialStderr) {
208
+ logStream.write(stripAnsiColors(initialStderr));
209
+ }
210
+
173
211
  const shell: BackgroundShell = {
174
212
  id,
175
213
  type: "shell",
@@ -179,8 +217,10 @@ export class BackgroundTaskManager {
179
217
  status: "running",
180
218
  stdout: initialStdout,
181
219
  stderr: initialStderr,
220
+ outputPath: logPath,
182
221
  onStop: () => {
183
222
  try {
223
+ logStream.end();
184
224
  if (child.pid) {
185
225
  process.kill(-child.pid, "SIGTERM");
186
226
  setTimeout(() => {
@@ -205,16 +245,27 @@ export class BackgroundTaskManager {
205
245
  this.notifyTasksChange();
206
246
 
207
247
  child.stdout?.on("data", (data) => {
208
- shell.stdout += stripAnsiColors(data.toString());
248
+ const stripped = stripAnsiColors(data.toString());
249
+ shell.stdout += stripped;
250
+ if (logStream.writable) {
251
+ logStream.write(stripped);
252
+ }
209
253
  this.notifyTasksChange();
210
254
  });
211
255
 
212
256
  child.stderr?.on("data", (data) => {
213
- shell.stderr += stripAnsiColors(data.toString());
257
+ const stripped = stripAnsiColors(data.toString());
258
+ shell.stderr += stripped;
259
+ if (logStream.writable) {
260
+ logStream.write(stripped);
261
+ }
214
262
  this.notifyTasksChange();
215
263
  });
216
264
 
217
265
  child.on("exit", (code) => {
266
+ if (logStream.writable) {
267
+ logStream.end();
268
+ }
218
269
  shell.status = code === 0 ? "completed" : "failed";
219
270
  shell.exitCode = code ?? 0;
220
271
  shell.endTime = Date.now();
@@ -223,8 +274,13 @@ export class BackgroundTaskManager {
223
274
  });
224
275
 
225
276
  child.on("error", (error) => {
277
+ const stripped = `\nProcess error: ${stripAnsiColors(error.message)}`;
226
278
  shell.status = "failed";
227
- shell.stderr += `\nProcess error: ${stripAnsiColors(error.message)}`;
279
+ shell.stderr += stripped;
280
+ if (logStream.writable) {
281
+ logStream.write(stripped);
282
+ logStream.end();
283
+ }
228
284
  shell.exitCode = 1;
229
285
  shell.endTime = Date.now();
230
286
  shell.runtime = shell.endTime - startTime;
@@ -12,7 +12,7 @@ import {
12
12
  type AgentToolBlockUpdateParams,
13
13
  generateMessageId,
14
14
  } from "../utils/messageOperations.js";
15
- import type { Message, Usage, SlashCommand } from "../types/index.js";
15
+ import type { Message, Usage } from "../types/index.js";
16
16
  import { join } from "path";
17
17
  import {
18
18
  appendMessages,
@@ -55,7 +55,6 @@ export interface MessageManagerCallbacks {
55
55
  onAddBangMessage?: (command: string) => void;
56
56
  onUpdateBangMessage?: (command: string, output: string) => void;
57
57
  onCompleteBangMessage?: (command: string, exitCode: number) => void;
58
- onSlashCommandsChange?: (commands: SlashCommand[]) => void;
59
58
  onInfoBlockAdded?: (content: string) => void;
60
59
  // Rewind callbacks
61
60
  onShowRewind?: () => void;
@@ -297,13 +296,6 @@ export class MessageManager {
297
296
  this.callbacks.onShowRewind?.();
298
297
  }
299
298
 
300
- /**
301
- * Trigger slash commands change callback
302
- */
303
- public triggerSlashCommandsChange(commands: SlashCommand[]): void {
304
- this.callbacks.onSlashCommandsChange?.(commands);
305
- }
306
-
307
299
  // Initialize state from session data
308
300
  public initializeFromSession(sessionData: SessionData): void {
309
301
  this.setSessionId(sessionData.id);
@@ -69,6 +69,7 @@ const DEFAULT_ALLOWED_RULES = [
69
69
  "Bash(whoami*)",
70
70
  "Bash(date*)",
71
71
  "Bash(uptime*)",
72
+ "Bash(wc -l*)",
72
73
  ];
73
74
 
74
75
  import { logger } from "../utils/globalLogger.js";
@@ -457,6 +458,7 @@ export class PermissionManager {
457
458
  permissionMode: PermissionMode,
458
459
  callback?: PermissionCallback,
459
460
  toolInput?: Record<string, unknown>,
461
+ toolCallId?: string,
460
462
  ): ToolPermissionContext {
461
463
  let suggestedPrefix: string | undefined;
462
464
  if (toolName === BASH_TOOL_NAME && toolInput?.command) {
@@ -475,6 +477,7 @@ export class PermissionManager {
475
477
  canUseToolCallback: callback,
476
478
  toolInput,
477
479
  suggestedPrefix,
480
+ toolCallId,
478
481
  };
479
482
 
480
483
  // Set hidePersistentOption for out-of-bounds file operations
@@ -512,8 +515,8 @@ export class PermissionManager {
512
515
  return true;
513
516
  }
514
517
 
515
- // Check out-of-bounds for cd and ls
516
- if (cmd === "cd" || cmd === "ls") {
518
+ // Check out-of-bounds for cd
519
+ if (cmd === "cd") {
517
520
  const pathArgs =
518
521
  (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter(
519
522
  (arg) => !arg.startsWith("-"),
@@ -642,19 +645,24 @@ export class PermissionManager {
642
645
  const args = commandMatch[2]?.trim() || "";
643
646
 
644
647
  if (SAFE_COMMANDS.includes(cmd)) {
645
- if (cmd === "pwd" || cmd === "true" || cmd === "false") {
648
+ if (
649
+ cmd === "pwd" ||
650
+ cmd === "true" ||
651
+ cmd === "false" ||
652
+ cmd === "ls"
653
+ ) {
646
654
  return true;
647
655
  }
648
656
 
649
657
  if (workdir) {
650
- // For cd and ls, check paths
658
+ // For cd, check paths
651
659
  const pathArgs =
652
660
  (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter(
653
661
  (arg) => !arg.startsWith("-"),
654
662
  ) || [];
655
663
 
656
664
  if (pathArgs.length === 0) {
657
- // cd or ls without arguments operates on current dir (workdir)
665
+ // cd without arguments operates on current dir (workdir)
658
666
  return true;
659
667
  }
660
668
 
@@ -739,10 +747,15 @@ export class PermissionManager {
739
747
  const args = commandMatch[2]?.trim() || "";
740
748
 
741
749
  if (SAFE_COMMANDS.includes(cmd)) {
742
- if (cmd === "pwd" || cmd === "true" || cmd === "false") {
750
+ if (
751
+ cmd === "pwd" ||
752
+ cmd === "true" ||
753
+ cmd === "false" ||
754
+ cmd === "ls"
755
+ ) {
743
756
  isSafe = true;
744
757
  } else {
745
- // For cd and ls, check paths
758
+ // For cd, check paths
746
759
  const pathArgs =
747
760
  (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter(
748
761
  (arg) => !arg.startsWith("-"),
@@ -775,7 +788,7 @@ export class PermissionManager {
775
788
  continue;
776
789
  }
777
790
 
778
- if (cmd === "cd" || cmd === "ls") {
791
+ if (cmd === "cd") {
779
792
  const pathArgs =
780
793
  (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter(
781
794
  (arg) => !arg.startsWith("-"),
@@ -243,8 +243,18 @@ export class SkillManager {
243
243
  const entries = await readdir(skillsPath, { withFileTypes: true });
244
244
 
245
245
  for (const entry of entries) {
246
+ const fullPath = join(skillsPath, entry.name);
246
247
  if (entry.isDirectory()) {
247
- directories.push(join(skillsPath, entry.name));
248
+ directories.push(fullPath);
249
+ } else if (entry.isSymbolicLink()) {
250
+ try {
251
+ const s = await stat(fullPath);
252
+ if (s.isDirectory()) {
253
+ directories.push(fullPath);
254
+ }
255
+ } catch {
256
+ // Ignore broken symlinks or other errors
257
+ }
248
258
  }
249
259
  }
250
260
  } catch {
@@ -277,7 +277,6 @@ export class SlashCommandManager {
277
277
  */
278
278
  public registerCommand(command: SlashCommand): void {
279
279
  this.commands.set(command.id, command);
280
- this.messageManager.triggerSlashCommandsChange(this.getCommands());
281
280
  }
282
281
 
283
282
  /**
@@ -1,4 +1,7 @@
1
1
  import { randomUUID } from "crypto";
2
+ import * as os from "os";
3
+ import * as fs from "fs";
4
+ import * as path from "path";
2
5
  import type { SubagentConfiguration } from "../utils/subagentParser.js";
3
6
  import type { Message, Usage } from "../types/index.js";
4
7
  import { AIManager } from "./aiManager.js";
@@ -70,6 +73,7 @@ export interface SubagentInstance {
70
73
  backgroundTaskId?: string; // ID of the background task if transitioned
71
74
  onUpdate?: () => void; // Optional callback for real-time updates
72
75
  model?: string; // Optional model override
76
+ logStream?: fs.WriteStream; // Optional log stream for background tasks
73
77
  }
74
78
 
75
79
  export interface SubagentManagerOptions {
@@ -274,6 +278,11 @@ export class SubagentManager {
274
278
  const taskId = backgroundTaskManager.generateId();
275
279
  const startTime = Date.now();
276
280
 
281
+ // Create log file
282
+ const logPath = path.join(os.tmpdir(), `wave-subagent-${taskId}.log`);
283
+ const logStream = fs.createWriteStream(logPath, { flags: "a" });
284
+ instance.logStream = logStream;
285
+
277
286
  backgroundTaskManager.addTask({
278
287
  id: taskId,
279
288
  type: "subagent",
@@ -282,8 +291,10 @@ export class SubagentManager {
282
291
  description: instance.description,
283
292
  stdout: "",
284
293
  stderr: "",
294
+ outputPath: logPath,
285
295
  subagentId: instance.subagentId,
286
296
  onStop: () => {
297
+ instance.logStream?.end();
287
298
  instance.aiManager.abortAIMessage();
288
299
  this.cleanupInstance(instance.subagentId);
289
300
  },
@@ -345,6 +356,11 @@ export class SubagentManager {
345
356
  const taskId = backgroundTaskManager.generateId();
346
357
  const startTime = Date.now();
347
358
 
359
+ // Create log file
360
+ const logPath = path.join(os.tmpdir(), `wave-subagent-${taskId}.log`);
361
+ const logStream = fs.createWriteStream(logPath, { flags: "a" });
362
+ instance.logStream = logStream;
363
+
348
364
  backgroundTaskManager.addTask({
349
365
  id: taskId,
350
366
  type: "subagent",
@@ -353,8 +369,10 @@ export class SubagentManager {
353
369
  description: instance.description,
354
370
  stdout: "",
355
371
  stderr: "",
372
+ outputPath: logPath,
356
373
  subagentId: instance.subagentId,
357
374
  onStop: () => {
375
+ instance.logStream?.end();
358
376
  instance.aiManager.abortAIMessage();
359
377
  this.cleanupInstance(instance.subagentId);
360
378
  },
@@ -446,6 +464,7 @@ export class SubagentManager {
446
464
 
447
465
  // If this was transitioned to background, update the background task
448
466
  if (instance.backgroundTaskId && backgroundTaskManager) {
467
+ instance.logStream?.end();
449
468
  const task = backgroundTaskManager.getTask(instance.backgroundTaskId);
450
469
  if (task) {
451
470
  task.status = "completed";
@@ -465,6 +484,7 @@ export class SubagentManager {
465
484
 
466
485
  // If this was transitioned to background, update the background task with error
467
486
  if (instance.backgroundTaskId && backgroundTaskManager) {
487
+ instance.logStream?.end();
468
488
  const task = backgroundTaskManager.getTask(instance.backgroundTaskId);
469
489
  if (task) {
470
490
  task.status = "failed";
@@ -599,6 +619,17 @@ export class SubagentManager {
599
619
  instance.lastTools.shift();
600
620
  }
601
621
  instance.onUpdate?.();
622
+
623
+ // Log tool execution to file
624
+ if (instance.logStream) {
625
+ const compactParams = (params.parameters || "{}").substring(
626
+ 0,
627
+ 100,
628
+ );
629
+ instance.logStream.write(
630
+ `[${new Date().toISOString()}] Running tool: ${params.name} with params: ${compactParams}${compactParams.length >= 100 ? "..." : ""}\n`,
631
+ );
632
+ }
602
633
  }
603
634
  }
604
635
 
@@ -195,6 +195,7 @@ class ToolManager {
195
195
  ? this.container.get<SkillManager>("SkillManager")
196
196
  : undefined,
197
197
  sessionId: context.sessionId,
198
+ toolCallId: context.toolCallId,
198
199
  };
199
200
 
200
201
  logger?.debug("Executing tool with enhanced context", {
@@ -798,6 +798,47 @@ export async function getFirstMessageContent(
798
798
  }
799
799
  }
800
800
 
801
+ /**
802
+ * Delete a session
803
+ * @param sessionId - UUID session identifier
804
+ * @param workdir - Working directory for the session
805
+ * @param sessionType - Type of session ("main" or "subagent", defaults to "main")
806
+ */
807
+ export async function deleteSession(
808
+ sessionId: string,
809
+ workdir: string,
810
+ sessionType: "main" | "subagent" = "main",
811
+ ): Promise<void> {
812
+ const filePath = await generateSessionFilePath(
813
+ sessionId,
814
+ workdir,
815
+ sessionType,
816
+ );
817
+ try {
818
+ await fs.unlink(filePath);
819
+ } catch (error) {
820
+ // Ignore if file doesn't exist
821
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
822
+ throw error;
823
+ }
824
+ }
825
+
826
+ // Also remove from index
827
+ try {
828
+ const encoder = new PathEncoder();
829
+ const projectDir = await encoder.getProjectDirectory(workdir, SESSION_DIR);
830
+ const indexPath = join(projectDir.encodedPath, SESSION_INDEX_FILENAME);
831
+ const indexContent = await fs.readFile(indexPath, "utf8");
832
+ const index = JSON.parse(indexContent) as SessionIndex;
833
+ if (index.sessions[sessionId]) {
834
+ delete index.sessions[sessionId];
835
+ await fs.writeFile(indexPath, JSON.stringify(index, null, 2), "utf8");
836
+ }
837
+ } catch {
838
+ // Ignore index errors
839
+ }
840
+ }
841
+
801
842
  /**
802
843
  * Truncate content to a maximum length, adding ellipsis if truncated
803
844
  * @param content - The content to truncate
@@ -182,10 +182,14 @@ When using the Agent tool, you must specify a subagent_type parameter to select
182
182
  id: instance.subagentId,
183
183
  backgroundHandler: async () => {
184
184
  isBackgrounded = true;
185
- await subagentManager.backgroundInstance(instance.subagentId);
185
+ const taskId = await subagentManager.backgroundInstance(
186
+ instance.subagentId,
187
+ );
188
+ const task = context.backgroundTaskManager?.getTask(taskId);
189
+ const outputPath = task?.outputPath;
186
190
  resolve({
187
191
  success: true,
188
- content: "Agent backgrounded",
192
+ content: `Agent backgrounded with ID: ${taskId}.${outputPath ? ` Real-time output: ${outputPath}` : ""}`,
189
193
  shortResult: "Agent backgrounded",
190
194
  isManuallyBackgrounded: true,
191
195
  });
@@ -206,9 +210,11 @@ When using the Agent tool, you must specify a subagent_type parameter to select
206
210
  }
207
211
 
208
212
  if (run_in_background) {
213
+ const task = context.backgroundTaskManager?.getTask(result);
214
+ const outputPath = task?.outputPath;
209
215
  resolve({
210
216
  success: true,
211
- content: `Agent started in background with ID: ${result}`,
217
+ content: `Agent started in background with ID: ${result}.${outputPath ? ` Real-time output: ${outputPath}` : ""}`,
212
218
  shortResult: `Agent started in background: ${result}`,
213
219
  });
214
220
  return;
@@ -106,6 +106,15 @@ Plan mode note: In plan mode, use this tool to clarify requirements or choose be
106
106
  metadata,
107
107
  } = args as unknown as AskUserQuestionInput;
108
108
 
109
+ if (!questions || !Array.isArray(questions) || questions.length === 0) {
110
+ return {
111
+ success: false,
112
+ content: "",
113
+ error:
114
+ "The 'questions' parameter is missing or empty. Please use the correct schema: { questions: [{ question, header, options, multiSelect? }] }",
115
+ };
116
+ }
117
+
109
118
  if (!context.permissionManager) {
110
119
  throw new Error(
111
120
  `Permission manager is required for ${ASK_USER_QUESTION_TOOL_NAME} tool`,
@@ -117,6 +126,7 @@ Plan mode note: In plan mode, use this tool to clarify requirements or choose be
117
126
  context.permissionMode || "default",
118
127
  context.canUseToolCallback,
119
128
  { questions, answers: existingAnswers, metadata },
129
+ context.toolCallId,
120
130
  );
121
131
  permissionContext.hidePersistentOption = true; // Always hide persistent option for questions
122
132
 
@@ -145,6 +145,7 @@ Usage notes:
145
145
  timeout,
146
146
  workdir: context.workdir,
147
147
  },
148
+ context.toolCallId,
148
149
  );
149
150
  const permissionResult =
150
151
  await context.permissionManager.checkPermission(permissionContext);
@@ -177,9 +178,11 @@ Usage notes:
177
178
  }
178
179
 
179
180
  const { id: taskId } = backgroundTaskManager.startShell(command, timeout);
181
+ const task = backgroundTaskManager.getTask(taskId);
182
+ const outputPath = task?.outputPath;
180
183
  return {
181
184
  success: true,
182
- content: `Command started in background with ID: ${taskId}. Use TaskOutput tool with task_id="${taskId}" to monitor output.`,
185
+ content: `Command started in background with ID: ${taskId}.${outputPath ? ` Real-time output: ${outputPath}` : ` Use TaskOutput tool with task_id="${taskId}" to monitor output.`}`,
183
186
  shortResult: `Background process ${taskId} started`,
184
187
  };
185
188
  }
@@ -220,9 +223,11 @@ Usage notes:
220
223
  outputBuffer,
221
224
  errorBuffer,
222
225
  );
226
+ const task = backgroundTaskManager.getTask(taskId);
227
+ const outputPath = task?.outputPath;
223
228
  resolve({
224
229
  success: true,
225
- content: `Command moved to background with ID: ${taskId}.`,
230
+ content: `Command moved to background with ID: ${taskId}.${outputPath ? ` Real-time output: ${outputPath}` : ""}`,
226
231
  shortResult: `Process ${taskId} backgrounded`,
227
232
  isManuallyBackgrounded: true,
228
233
  });
@@ -174,6 +174,7 @@ Usage:
174
174
  replace_all: replaceAll,
175
175
  startLineNumber,
176
176
  },
177
+ context.toolCallId,
177
178
  );
178
179
  const permissionResult =
179
180
  await context.permissionManager.checkPermission(permissionContext);
@@ -86,6 +86,7 @@ Ensure your plan is complete and unambiguous:
86
86
  context.permissionMode || "plan",
87
87
  context.canUseToolCallback,
88
88
  { plan_content: planContent },
89
+ context.toolCallId,
89
90
  );
90
91
 
91
92
  const permissionResult =
@@ -1,70 +1,13 @@
1
- import { spawn } from "child_process";
2
- import { rgPath } from "../utils/ripgrep.js";
1
+ import { glob } from "glob";
3
2
  import { stat } from "fs/promises";
4
3
  import type { ToolPlugin, ToolResult, ToolContext } from "./types.js";
5
4
  import { resolvePath, getDisplayPath } from "../utils/path.js";
6
- import { getAllIgnorePatterns } from "../utils/fileFilter.js";
7
5
  import { GLOB_TOOL_NAME } from "../constants/tools.js";
8
6
 
9
7
  /**
10
8
  * Maximum number of files returned by glob tool
11
9
  */
12
- const MAX_GLOB_RESULTS = 1000;
13
-
14
- /**
15
- * Execute ripgrep to find files matching a pattern
16
- */
17
- async function runRipgrep(pattern: string, workdir: string): Promise<string[]> {
18
- if (!rgPath) {
19
- throw new Error("ripgrep is not available");
20
- }
21
-
22
- const ignorePatterns = getAllIgnorePatterns();
23
- const rgArgs = ["--files", "--color=never", "--hidden", "--glob", pattern];
24
-
25
- for (const ignorePattern of ignorePatterns) {
26
- rgArgs.push("--glob", `!${ignorePattern}`);
27
- }
28
-
29
- return new Promise<string[]>((resolve, reject) => {
30
- const child = spawn(rgPath, rgArgs, {
31
- cwd: workdir,
32
- stdio: ["ignore", "pipe", "pipe"],
33
- });
34
-
35
- let stdout = "";
36
- let stderr = "";
37
-
38
- child.stdout?.on("data", (data) => {
39
- stdout += data.toString();
40
- });
41
-
42
- child.stderr?.on("data", (data) => {
43
- stderr += data.toString();
44
- });
45
-
46
- child.on("close", (code) => {
47
- if (code !== 0 && code !== 1) {
48
- reject(
49
- new Error(
50
- `ripgrep failed with code ${code}: ${stderr || "Unknown error"}`,
51
- ),
52
- );
53
- return;
54
- }
55
- const files = stdout
56
- .trim()
57
- .split("\n")
58
- .filter((f) => f.length > 0)
59
- .map((f) => f.replace(/\\/g, "/")); // Normalize to forward slashes
60
- resolve(files);
61
- });
62
-
63
- child.on("error", (err) => {
64
- reject(err);
65
- });
66
- });
67
- }
10
+ const MAX_GLOB_RESULTS = 100;
68
11
 
69
12
  /**
70
13
  * Glob Tool Plugin - Fast file pattern matching
@@ -122,8 +65,13 @@ export const globTool: ToolPlugin = {
122
65
  ? resolvePath(searchPath, context.workdir)
123
66
  : context.workdir;
124
67
 
125
- // Execute glob search using ripgrep
126
- const matches = await runRipgrep(pattern, workdir);
68
+ // Execute glob search using glob package
69
+ const matches = await glob(pattern, {
70
+ cwd: workdir,
71
+ nodir: true,
72
+ dot: true,
73
+ ignore: ["**/.git/**"],
74
+ });
127
75
 
128
76
  if (matches.length === 0) {
129
77
  return {
@@ -1,6 +1,5 @@
1
1
  import type { ToolPlugin, ToolResult, ToolContext } from "./types.js";
2
2
  import { spawn } from "child_process";
3
- import { getAllIgnorePatterns } from "../utils/fileFilter.js";
4
3
  import { rgPath } from "../utils/ripgrep.js";
5
4
  import { getDisplayPath } from "../utils/path.js";
6
5
  import {
@@ -188,12 +187,6 @@ export const grepTool: ToolPlugin = {
188
187
  rgArgs.push("--glob", globPattern);
189
188
  }
190
189
 
191
- // Get common ignore rules
192
- const ignorePatterns = getAllIgnorePatterns();
193
- for (const exclude of ignorePatterns) {
194
- rgArgs.push("--glob", `!${exclude}`);
195
- }
196
-
197
190
  // Add search pattern - use -e parameter to avoid patterns starting with - being mistaken as command line options
198
191
  rgArgs.push("-e", pattern);
199
192
 
@@ -79,6 +79,8 @@ export interface ToolContext {
79
79
  skillManager?: import("../managers/skillManager.js").SkillManager;
80
80
  /** Current session ID */
81
81
  sessionId?: string;
82
+ /** The ID of the current tool call */
83
+ toolCallId?: string;
82
84
  /** Callback to update the short result of the current tool block */
83
85
  onShortResultUpdate?: (shortResult: string) => void;
84
86
  }