wave-agent-sdk 0.4.0 → 0.5.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 (105) hide show
  1. package/dist/agent.d.ts +28 -5
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +54 -37
  4. package/dist/constants/tools.d.ts +2 -2
  5. package/dist/constants/tools.js +2 -2
  6. package/dist/managers/MemoryRuleManager.js +1 -1
  7. package/dist/managers/aiManager.d.ts +3 -3
  8. package/dist/managers/aiManager.d.ts.map +1 -1
  9. package/dist/managers/aiManager.js +3 -4
  10. package/dist/managers/backgroundBashManager.d.ts.map +1 -1
  11. package/dist/managers/backgroundBashManager.js +1 -0
  12. package/dist/managers/backgroundTaskManager.d.ts +35 -0
  13. package/dist/managers/backgroundTaskManager.d.ts.map +1 -0
  14. package/dist/managers/backgroundTaskManager.js +249 -0
  15. package/dist/managers/foregroundTaskManager.d.ts +9 -0
  16. package/dist/managers/foregroundTaskManager.d.ts.map +1 -0
  17. package/dist/managers/foregroundTaskManager.js +20 -0
  18. package/dist/managers/liveConfigManager.d.ts +1 -1
  19. package/dist/managers/lspManager.d.ts.map +1 -1
  20. package/dist/managers/lspManager.js +3 -1
  21. package/dist/managers/messageManager.d.ts +12 -2
  22. package/dist/managers/messageManager.d.ts.map +1 -1
  23. package/dist/managers/messageManager.js +36 -2
  24. package/dist/managers/permissionManager.d.ts.map +1 -1
  25. package/dist/managers/permissionManager.js +1 -7
  26. package/dist/managers/pluginManager.d.ts.map +1 -1
  27. package/dist/managers/pluginManager.js +3 -2
  28. package/dist/managers/slashCommandManager.d.ts +3 -0
  29. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  30. package/dist/managers/slashCommandManager.js +1 -0
  31. package/dist/managers/subagentManager.d.ts +11 -2
  32. package/dist/managers/subagentManager.d.ts.map +1 -1
  33. package/dist/managers/subagentManager.js +137 -39
  34. package/dist/managers/toolManager.d.ts +7 -1
  35. package/dist/managers/toolManager.d.ts.map +1 -1
  36. package/dist/managers/toolManager.js +9 -3
  37. package/dist/services/GitService.d.ts.map +1 -1
  38. package/dist/services/GitService.js +6 -2
  39. package/dist/services/MarketplaceService.d.ts +2 -2
  40. package/dist/services/MarketplaceService.d.ts.map +1 -1
  41. package/dist/services/MarketplaceService.js +18 -11
  42. package/dist/services/MemoryRuleService.d.ts +1 -1
  43. package/dist/services/MemoryRuleService.d.ts.map +1 -1
  44. package/dist/services/MemoryRuleService.js +13 -2
  45. package/dist/services/memory.js +1 -1
  46. package/dist/tools/bashTool.d.ts +0 -8
  47. package/dist/tools/bashTool.d.ts.map +1 -1
  48. package/dist/tools/bashTool.js +44 -172
  49. package/dist/tools/editTool.d.ts.map +1 -1
  50. package/dist/tools/editTool.js +6 -5
  51. package/dist/tools/multiEditTool.d.ts.map +1 -1
  52. package/dist/tools/multiEditTool.js +7 -6
  53. package/dist/tools/taskOutputTool.d.ts +3 -0
  54. package/dist/tools/taskOutputTool.d.ts.map +1 -0
  55. package/dist/tools/taskOutputTool.js +149 -0
  56. package/dist/tools/taskStopTool.d.ts +3 -0
  57. package/dist/tools/taskStopTool.d.ts.map +1 -0
  58. package/dist/tools/taskStopTool.js +65 -0
  59. package/dist/tools/taskTool.d.ts.map +1 -1
  60. package/dist/tools/taskTool.js +105 -63
  61. package/dist/tools/types.d.ts +3 -0
  62. package/dist/tools/types.d.ts.map +1 -1
  63. package/dist/types/marketplace.d.ts +1 -0
  64. package/dist/types/marketplace.d.ts.map +1 -1
  65. package/dist/types/messaging.d.ts +1 -0
  66. package/dist/types/messaging.d.ts.map +1 -1
  67. package/dist/types/processes.d.ts +24 -4
  68. package/dist/types/processes.d.ts.map +1 -1
  69. package/dist/utils/editUtils.d.ts +2 -11
  70. package/dist/utils/editUtils.d.ts.map +1 -1
  71. package/dist/utils/editUtils.js +52 -79
  72. package/dist/utils/messageOperations.d.ts +3 -1
  73. package/dist/utils/messageOperations.d.ts.map +1 -1
  74. package/dist/utils/messageOperations.js +5 -1
  75. package/package.json +5 -5
  76. package/src/agent.ts +73 -45
  77. package/src/constants/tools.ts +2 -2
  78. package/src/managers/MemoryRuleManager.ts +1 -1
  79. package/src/managers/aiManager.ts +6 -9
  80. package/src/managers/backgroundBashManager.ts +1 -0
  81. package/src/managers/backgroundTaskManager.ts +306 -0
  82. package/src/managers/foregroundTaskManager.ts +26 -0
  83. package/src/managers/lspManager.ts +3 -1
  84. package/src/managers/messageManager.ts +48 -2
  85. package/src/managers/permissionManager.ts +1 -7
  86. package/src/managers/pluginManager.ts +4 -3
  87. package/src/managers/slashCommandManager.ts +4 -0
  88. package/src/managers/subagentManager.ts +167 -35
  89. package/src/managers/toolManager.ts +16 -4
  90. package/src/services/GitService.ts +6 -2
  91. package/src/services/MarketplaceService.ts +30 -12
  92. package/src/services/MemoryRuleService.ts +18 -6
  93. package/src/services/memory.ts +1 -1
  94. package/src/tools/bashTool.ts +59 -196
  95. package/src/tools/editTool.ts +6 -17
  96. package/src/tools/multiEditTool.ts +7 -18
  97. package/src/tools/taskOutputTool.ts +174 -0
  98. package/src/tools/taskStopTool.ts +72 -0
  99. package/src/tools/taskTool.ts +130 -74
  100. package/src/tools/types.ts +3 -0
  101. package/src/types/marketplace.ts +1 -0
  102. package/src/types/messaging.ts +1 -0
  103. package/src/types/processes.ts +33 -4
  104. package/src/utils/editUtils.ts +65 -103
  105. package/src/utils/messageOperations.ts +7 -0
@@ -1,4 +1,5 @@
1
1
  import { minimatch } from "minimatch";
2
+ import * as path from "node:path";
2
3
  import { parseFrontmatter } from "../utils/markdownParser.js";
3
4
  import type { MemoryRule, MemoryRuleMetadata } from "../types/memoryRule.js";
4
5
 
@@ -45,15 +46,26 @@ export class MemoryRuleService {
45
46
  /**
46
47
  * Determines if a rule matches any of the given file paths using minimatch.
47
48
  */
48
- isRuleActive(rule: MemoryRule, filesInContext: string[]): boolean {
49
+ isRuleActive(
50
+ rule: MemoryRule,
51
+ filesInContext: string[],
52
+ workdir?: string,
53
+ ): boolean {
49
54
  if (!rule.metadata.paths || rule.metadata.paths.length === 0) {
50
55
  return true;
51
56
  }
52
57
 
53
- return filesInContext.some((filePath) =>
54
- rule.metadata.paths!.some((pattern) =>
55
- minimatch(filePath, pattern, { dot: true }),
56
- ),
57
- );
58
+ return filesInContext.some((filePath) => {
59
+ // Normalize path relative to workdir if it's an absolute path
60
+ let normalizedPath = filePath;
61
+ if (workdir && path.isAbsolute(filePath)) {
62
+ normalizedPath = path.relative(workdir, filePath);
63
+ }
64
+
65
+ return rule.metadata.paths!.some((pattern) => {
66
+ const isMatch = minimatch(normalizedPath, pattern, { dot: true });
67
+ return isMatch;
68
+ });
69
+ });
58
70
  }
59
71
  }
@@ -142,7 +142,7 @@ export const readMemoryFile = async (workdir: string): Promise<string> => {
142
142
  return "";
143
143
  }
144
144
  logger.error("Failed to read memory file", { memoryFilePath, error });
145
- throw error;
145
+ return "";
146
146
  }
147
147
  };
148
148
 
@@ -4,8 +4,7 @@ import { stripAnsiColors } from "../utils/stringUtils.js";
4
4
  import type { ToolPlugin, ToolResult, ToolContext } from "./types.js";
5
5
  import {
6
6
  BASH_TOOL_NAME,
7
- BASH_OUTPUT_TOOL_NAME,
8
- KILL_BASH_TOOL_NAME,
7
+ TASK_OUTPUT_TOOL_NAME,
9
8
  GLOB_TOOL_NAME,
10
9
  GREP_TOOL_NAME,
11
10
  READ_TOOL_NAME,
@@ -89,7 +88,7 @@ Usage notes:
89
88
  },
90
89
  run_in_background: {
91
90
  type: "boolean",
92
- description: `Set to true to run this command in the background. Use ${BASH_OUTPUT_TOOL_NAME} to read the output later.`,
91
+ description: `Set to true to run this command in the background. Use ${TASK_OUTPUT_TOOL_NAME} to read the output later.`,
93
92
  },
94
93
  },
95
94
  required: ["command"],
@@ -164,20 +163,20 @@ Usage notes:
164
163
 
165
164
  if (runInBackground) {
166
165
  // Background execution
167
- const backgroundBashManager = context?.backgroundBashManager;
168
- if (!backgroundBashManager) {
166
+ const backgroundTaskManager = context?.backgroundTaskManager;
167
+ if (!backgroundTaskManager) {
169
168
  return {
170
169
  success: false,
171
170
  content: "",
172
- error: "Background bash manager not available",
171
+ error: "Background task manager not available",
173
172
  };
174
173
  }
175
174
 
176
- const shellId = backgroundBashManager.startShell(command, timeout);
175
+ const { id: taskId } = backgroundTaskManager.startShell(command, timeout);
177
176
  return {
178
177
  success: true,
179
- content: `Command started in background with ID: ${shellId}. Use ${BASH_OUTPUT_TOOL_NAME} tool with bash_id="${shellId}" to monitor output.`,
180
- shortResult: `Background process ${shellId} started`,
178
+ content: `Command started in background with ID: ${taskId}. Use TaskOutput tool with task_id="${taskId}" to monitor output.`,
179
+ shortResult: `Background process ${taskId} started`,
181
180
  };
182
181
  }
183
182
 
@@ -195,6 +194,41 @@ Usage notes:
195
194
  let outputBuffer = "";
196
195
  let errorBuffer = "";
197
196
  let isAborted = false;
197
+ let isBackgrounded = false;
198
+
199
+ const foregroundTaskId = `bash_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
200
+
201
+ // Register as foreground task
202
+ if (context.foregroundTaskManager && command) {
203
+ context.foregroundTaskManager.registerForegroundTask({
204
+ id: foregroundTaskId,
205
+ backgroundHandler: async () => {
206
+ isBackgrounded = true;
207
+ if (timeoutHandle) {
208
+ clearTimeout(timeoutHandle);
209
+ }
210
+
211
+ const backgroundTaskManager = context.backgroundTaskManager;
212
+ if (backgroundTaskManager) {
213
+ const taskId = backgroundTaskManager.adoptProcess(
214
+ child,
215
+ command,
216
+ outputBuffer,
217
+ errorBuffer,
218
+ );
219
+ resolve({
220
+ success: true,
221
+ content: `Command moved to background with ID: ${taskId}. Use TaskOutput tool with task_id="${taskId}" to monitor output.`,
222
+ shortResult: `Process ${taskId} backgrounded`,
223
+ });
224
+ } else {
225
+ handleAbort(
226
+ "Failed to background: Background task manager not available",
227
+ );
228
+ }
229
+ },
230
+ });
231
+ }
198
232
 
199
233
  // Set up timeout
200
234
  let timeoutHandle: NodeJS.Timeout | undefined;
@@ -267,19 +301,25 @@ Usage notes:
267
301
  }
268
302
 
269
303
  child.stdout?.on("data", (data) => {
270
- if (!isAborted) {
304
+ if (!isAborted && !isBackgrounded) {
271
305
  outputBuffer += stripAnsiColors(data.toString());
272
306
  }
273
307
  });
274
308
 
275
309
  child.stderr?.on("data", (data) => {
276
- if (!isAborted) {
310
+ if (!isAborted && !isBackgrounded) {
277
311
  errorBuffer += stripAnsiColors(data.toString());
278
312
  }
279
313
  });
280
314
 
281
315
  child.on("exit", (code) => {
282
- if (!isAborted) {
316
+ if (context.foregroundTaskManager) {
317
+ context.foregroundTaskManager.unregisterForegroundTask(
318
+ foregroundTaskId,
319
+ );
320
+ }
321
+
322
+ if (!isAborted && !isBackgrounded) {
283
323
  if (timeoutHandle) {
284
324
  clearTimeout(timeoutHandle);
285
325
  }
@@ -309,7 +349,13 @@ Usage notes:
309
349
  });
310
350
 
311
351
  child.on("error", (error) => {
312
- if (!isAborted) {
352
+ if (context.foregroundTaskManager) {
353
+ context.foregroundTaskManager.unregisterForegroundTask(
354
+ foregroundTaskId,
355
+ );
356
+ }
357
+
358
+ if (!isAborted && !isBackgrounded) {
313
359
  if (timeoutHandle) {
314
360
  clearTimeout(timeoutHandle);
315
361
  }
@@ -334,186 +380,3 @@ Usage notes:
334
380
  return `${command}${runInBackground ? " (background)" : ""}`;
335
381
  },
336
382
  };
337
-
338
- /**
339
- * BashOutput tool - retrieves output from background bash shells
340
- */
341
- export const bashOutputTool: ToolPlugin = {
342
- name: BASH_OUTPUT_TOOL_NAME,
343
- config: {
344
- type: "function",
345
- function: {
346
- name: BASH_OUTPUT_TOOL_NAME,
347
- description:
348
- "Retrieves output from a running or completed background bash shell",
349
- parameters: {
350
- type: "object",
351
- properties: {
352
- bash_id: {
353
- type: "string",
354
- description:
355
- "The ID of the background shell to retrieve output from",
356
- },
357
- filter: {
358
- type: "string",
359
- description:
360
- "Optional regular expression to filter the output lines. Only lines matching this regex will be included in the result. Any lines that do not match will no longer be available to read.",
361
- },
362
- },
363
- required: ["bash_id"],
364
- },
365
- },
366
- },
367
- execute: async (
368
- args: Record<string, unknown>,
369
- context: ToolContext,
370
- ): Promise<ToolResult> => {
371
- const bashId = args.bash_id as string;
372
- const filter = args.filter as string | undefined;
373
-
374
- if (!bashId || typeof bashId !== "string") {
375
- return {
376
- success: false,
377
- content: "",
378
- error: "bash_id parameter is required and must be a string",
379
- };
380
- }
381
-
382
- const backgroundBashManager = context?.backgroundBashManager;
383
- if (!backgroundBashManager) {
384
- return {
385
- success: false,
386
- content: "",
387
- error: "Background bash manager not available",
388
- };
389
- }
390
-
391
- const output = backgroundBashManager.getOutput(bashId, filter);
392
- if (!output) {
393
- return {
394
- success: false,
395
- content: "",
396
- error: `Background shell with ID ${bashId} not found`,
397
- };
398
- }
399
-
400
- const shell = backgroundBashManager.getShell(bashId);
401
- if (!shell) {
402
- return {
403
- success: false,
404
- content: "",
405
- error: `Background shell with ID ${bashId} not found`,
406
- };
407
- }
408
-
409
- let content = "";
410
- if (output.stdout) {
411
- content += stripAnsiColors(output.stdout);
412
- }
413
- if (output.stderr) {
414
- content += (content ? "\n" : "") + stripAnsiColors(output.stderr);
415
- }
416
-
417
- const finalContent = content || "No output available";
418
- const processedContent =
419
- finalContent.length > MAX_OUTPUT_LENGTH
420
- ? finalContent.substring(0, MAX_OUTPUT_LENGTH) +
421
- "\n\n... (output truncated)"
422
- : finalContent;
423
-
424
- return {
425
- success: true,
426
- content: processedContent,
427
- shortResult: `${bashId}: ${output.status}${shell.exitCode !== undefined ? ` (${shell.exitCode})` : ""}`,
428
- error: undefined,
429
- };
430
- },
431
- formatCompactParams: (params: Record<string, unknown>) => {
432
- const bashId = params.bash_id as string;
433
- const filter = params.filter as string | undefined;
434
- return filter ? `${bashId} filtered: ${filter}` : bashId;
435
- },
436
- };
437
-
438
- /**
439
- * KillBash tool - kills a running background bash shell
440
- */
441
- export const killBashTool: ToolPlugin = {
442
- name: KILL_BASH_TOOL_NAME,
443
- config: {
444
- type: "function",
445
- function: {
446
- name: KILL_BASH_TOOL_NAME,
447
- description: "Kills a running background bash shell by its ID",
448
- parameters: {
449
- type: "object",
450
- properties: {
451
- shell_id: {
452
- type: "string",
453
- description: "The ID of the background shell to kill",
454
- },
455
- },
456
- required: ["shell_id"],
457
- },
458
- },
459
- },
460
- execute: async (
461
- args: Record<string, unknown>,
462
- context: ToolContext,
463
- ): Promise<ToolResult> => {
464
- const shellId = args.shell_id as string;
465
-
466
- if (!shellId || typeof shellId !== "string") {
467
- return {
468
- success: false,
469
- content: "",
470
- error: "shell_id parameter is required and must be a string",
471
- };
472
- }
473
-
474
- const backgroundBashManager = context?.backgroundBashManager;
475
- if (!backgroundBashManager) {
476
- return {
477
- success: false,
478
- content: "",
479
- error: "Background bash manager not available",
480
- };
481
- }
482
-
483
- const shell = backgroundBashManager.getShell(shellId);
484
- if (!shell) {
485
- return {
486
- success: false,
487
- content: "",
488
- error: `Background shell with ID ${shellId} not found`,
489
- };
490
- }
491
-
492
- if (shell.status !== "running") {
493
- return {
494
- success: false,
495
- content: "",
496
- error: `Background shell ${shellId} is not running (status: ${shell.status})`,
497
- };
498
- }
499
-
500
- const killed = backgroundBashManager.killShell(shellId);
501
- if (killed) {
502
- return {
503
- success: true,
504
- content: `Background shell ${shellId} has been killed`,
505
- shortResult: `Killed ${shellId}`,
506
- };
507
- } else {
508
- return {
509
- success: false,
510
- content: "",
511
- error: `Failed to kill background shell ${shellId}`,
512
- };
513
- }
514
- },
515
- formatCompactParams: (params: Record<string, unknown>) => {
516
- const shellId = params.shell_id as string;
517
- return shellId;
518
- },
519
- };
@@ -2,11 +2,7 @@ import { readFile, writeFile } from "fs/promises";
2
2
  import { logger } from "../utils/globalLogger.js";
3
3
  import type { ToolPlugin, ToolResult, ToolContext } from "./types.js";
4
4
  import { resolvePath, getDisplayPath } from "../utils/path.js";
5
- import {
6
- findIndentationInsensitiveMatch,
7
- escapeRegExp,
8
- saveEditErrorSnapshot,
9
- } from "../utils/editUtils.js";
5
+ import { escapeRegExp, analyzeEditMismatch } from "../utils/editUtils.js";
10
6
  import { EDIT_TOOL_NAME, READ_TOOL_NAME } from "../constants/tools.js";
11
7
 
12
8
  /**
@@ -115,23 +111,16 @@ export const editTool: ToolPlugin = {
115
111
  };
116
112
  }
117
113
 
118
- // Check if old_string exists (with smart indentation matching)
119
- const matchedOldString = findIndentationInsensitiveMatch(
120
- originalContent,
121
- oldString,
122
- );
114
+ // Check if old_string exists
115
+ const matchedOldString = originalContent.includes(oldString)
116
+ ? oldString
117
+ : null;
123
118
 
124
119
  if (!matchedOldString) {
125
- await saveEditErrorSnapshot(
126
- resolvedPath,
127
- oldString,
128
- originalContent,
129
- EDIT_TOOL_NAME,
130
- );
131
120
  return {
132
121
  success: false,
133
122
  content: "",
134
- error: `old_string not found in file`,
123
+ error: analyzeEditMismatch(originalContent, oldString),
135
124
  };
136
125
  }
137
126
 
@@ -2,11 +2,7 @@ import { readFile, writeFile } from "fs/promises";
2
2
  import { logger } from "../utils/globalLogger.js";
3
3
  import type { ToolPlugin, ToolResult, ToolContext } from "./types.js";
4
4
  import { resolvePath, getDisplayPath } from "../utils/path.js";
5
- import {
6
- findIndentationInsensitiveMatch,
7
- escapeRegExp,
8
- saveEditErrorSnapshot,
9
- } from "../utils/editUtils.js";
5
+ import { escapeRegExp, analyzeEditMismatch } from "../utils/editUtils.js";
10
6
  import {
11
7
  MULTI_EDIT_TOOL_NAME,
12
8
  EDIT_TOOL_NAME,
@@ -185,23 +181,16 @@ export const multiEditTool: ToolPlugin = {
185
181
  continue;
186
182
  }
187
183
 
188
- // Check if old_string exists (with smart indentation matching)
189
- const matchedOldString = findIndentationInsensitiveMatch(
190
- currentContent,
191
- edit.old_string,
192
- );
184
+ // Check if old_string exists
185
+ const matchedOldString = currentContent.includes(edit.old_string)
186
+ ? edit.old_string
187
+ : null;
193
188
 
194
189
  if (!matchedOldString) {
195
- await saveEditErrorSnapshot(
196
- resolvedPath,
197
- edit.old_string,
198
- currentContent,
199
- MULTI_EDIT_TOOL_NAME,
200
- );
201
190
  return {
202
191
  success: false,
203
192
  content: "",
204
- error: `Edit operation ${i + 1}: old_string not found in current content`,
193
+ error: `Edit operation ${i + 1}: ${analyzeEditMismatch(currentContent, edit.old_string)}`,
205
194
  };
206
195
  }
207
196
 
@@ -209,7 +198,7 @@ export const multiEditTool: ToolPlugin = {
209
198
 
210
199
  if (replaceAll) {
211
200
  // Replace all matches
212
- const regex = new RegExp(escapeRegExp(matchedOldString), "g");
201
+ const regex = new RegExp(escapeRegExp(edit.old_string), "g");
213
202
  currentContent = currentContent.replace(regex, edit.new_string);
214
203
  replacementCount = (currentContent.match(regex) || []).length;
215
204
  appliedEdits.push(
@@ -0,0 +1,174 @@
1
+ import { TASK_OUTPUT_TOOL_NAME } from "../constants/tools.js";
2
+ import { ToolContext, ToolPlugin, ToolResult } from "./types.js";
3
+ import { stripAnsiColors } from "../utils/stringUtils.js";
4
+
5
+ const MAX_OUTPUT_LENGTH = 30000;
6
+
7
+ export const taskOutputTool: ToolPlugin = {
8
+ name: TASK_OUTPUT_TOOL_NAME,
9
+ config: {
10
+ type: "function",
11
+ function: {
12
+ name: TASK_OUTPUT_TOOL_NAME,
13
+ description:
14
+ "Retrieves output from a running or completed background task",
15
+ parameters: {
16
+ type: "object",
17
+ properties: {
18
+ task_id: {
19
+ type: "string",
20
+ description:
21
+ "The ID of the background task to retrieve output from",
22
+ },
23
+ filter: {
24
+ type: "string",
25
+ description:
26
+ "Optional regular expression to filter the output lines.",
27
+ },
28
+ block: {
29
+ type: "boolean",
30
+ description:
31
+ "If true, wait for the task to complete before returning output. If false, return current output immediately.",
32
+ },
33
+ },
34
+ required: ["task_id"],
35
+ },
36
+ },
37
+ },
38
+ execute: async (
39
+ args: Record<string, unknown>,
40
+ context: ToolContext,
41
+ ): Promise<ToolResult> => {
42
+ const taskId = args.task_id as string;
43
+ const filter = args.filter as string | undefined;
44
+ const block = args.block as boolean | undefined;
45
+
46
+ if (!taskId || typeof taskId !== "string") {
47
+ return {
48
+ success: false,
49
+ content: "",
50
+ error: "task_id parameter is required and must be a string",
51
+ };
52
+ }
53
+
54
+ const backgroundTaskManager = context?.backgroundTaskManager;
55
+ if (!backgroundTaskManager) {
56
+ return {
57
+ success: false,
58
+ content: "",
59
+ error: "Background task manager not available",
60
+ };
61
+ }
62
+
63
+ const getResult = () => {
64
+ const output = backgroundTaskManager.getOutput(taskId, filter);
65
+ if (!output) return null;
66
+
67
+ let content = "";
68
+ if (output.stdout) {
69
+ content += stripAnsiColors(output.stdout);
70
+ }
71
+ if (output.stderr) {
72
+ content += (content ? "\n" : "") + stripAnsiColors(output.stderr);
73
+ }
74
+
75
+ const finalContent = content || "No output available";
76
+ const processedContent =
77
+ finalContent.length > MAX_OUTPUT_LENGTH
78
+ ? finalContent.substring(0, MAX_OUTPUT_LENGTH) +
79
+ "\n\n... (output truncated)"
80
+ : finalContent;
81
+
82
+ return {
83
+ success: true,
84
+ content: processedContent,
85
+ shortResult: `${taskId}: ${output.status}`,
86
+ };
87
+ };
88
+
89
+ if (block) {
90
+ // Polling for completion
91
+ return new Promise((resolve) => {
92
+ let timeoutHandle: NodeJS.Timeout | null = null;
93
+ let isAborted = false;
94
+
95
+ const cleanup = () => {
96
+ if (timeoutHandle) {
97
+ clearTimeout(timeoutHandle);
98
+ timeoutHandle = null;
99
+ }
100
+ };
101
+
102
+ const onAbort = () => {
103
+ isAborted = true;
104
+ cleanup();
105
+ resolve({
106
+ success: false,
107
+ content: "",
108
+ error: "Task output retrieval was aborted",
109
+ });
110
+ };
111
+
112
+ if (context.abortSignal) {
113
+ if (context.abortSignal.aborted) {
114
+ onAbort();
115
+ return;
116
+ }
117
+ context.abortSignal.addEventListener("abort", onAbort, {
118
+ once: true,
119
+ });
120
+ }
121
+
122
+ const check = () => {
123
+ if (isAborted) return;
124
+
125
+ const task = backgroundTaskManager.getTask(taskId);
126
+ if (!task) {
127
+ if (context.abortSignal) {
128
+ context.abortSignal.removeEventListener("abort", onAbort);
129
+ }
130
+ resolve({
131
+ success: false,
132
+ content: "",
133
+ error: `Task with ID ${taskId} not found`,
134
+ });
135
+ return;
136
+ }
137
+
138
+ if (task.status !== "running") {
139
+ if (context.abortSignal) {
140
+ context.abortSignal.removeEventListener("abort", onAbort);
141
+ }
142
+ const result = getResult();
143
+ resolve(
144
+ result || {
145
+ success: false,
146
+ content: "",
147
+ error: "Task not found",
148
+ },
149
+ );
150
+ } else {
151
+ timeoutHandle = setTimeout(check, 500);
152
+ }
153
+ };
154
+ check();
155
+ });
156
+ }
157
+
158
+ const result = getResult();
159
+ if (!result) {
160
+ return {
161
+ success: false,
162
+ content: "",
163
+ error: `Task with ID ${taskId} not found`,
164
+ };
165
+ }
166
+
167
+ return result;
168
+ },
169
+ formatCompactParams: (params: Record<string, unknown>) => {
170
+ const taskId = params.task_id as string;
171
+ const block = params.block as boolean;
172
+ return `${taskId}${block ? " (blocking)" : ""}`;
173
+ },
174
+ };
@@ -0,0 +1,72 @@
1
+ import { TASK_STOP_TOOL_NAME } from "../constants/tools.js";
2
+ import { ToolContext, ToolPlugin, ToolResult } from "./types.js";
3
+
4
+ export const taskStopTool: ToolPlugin = {
5
+ name: TASK_STOP_TOOL_NAME,
6
+ config: {
7
+ type: "function",
8
+ function: {
9
+ name: TASK_STOP_TOOL_NAME,
10
+ description: "Stops a running background task",
11
+ parameters: {
12
+ type: "object",
13
+ properties: {
14
+ task_id: {
15
+ type: "string",
16
+ description: "The ID of the background task to stop",
17
+ },
18
+ },
19
+ required: ["task_id"],
20
+ },
21
+ },
22
+ },
23
+ execute: async (
24
+ args: Record<string, unknown>,
25
+ context: ToolContext,
26
+ ): Promise<ToolResult> => {
27
+ const taskId = args.task_id as string;
28
+
29
+ if (!taskId || typeof taskId !== "string") {
30
+ return {
31
+ success: false,
32
+ content: "",
33
+ error: "task_id parameter is required and must be a string",
34
+ };
35
+ }
36
+
37
+ const backgroundTaskManager = context?.backgroundTaskManager;
38
+ if (!backgroundTaskManager) {
39
+ return {
40
+ success: false,
41
+ content: "",
42
+ error: "Background task manager not available",
43
+ };
44
+ }
45
+
46
+ const stopped = backgroundTaskManager.stopTask(taskId);
47
+ if (stopped) {
48
+ return {
49
+ success: true,
50
+ content: `Task ${taskId} has been stopped`,
51
+ shortResult: `Stopped ${taskId}`,
52
+ };
53
+ } else {
54
+ const task = backgroundTaskManager.getTask(taskId);
55
+ if (!task) {
56
+ return {
57
+ success: false,
58
+ content: "",
59
+ error: `Task with ID ${taskId} not found`,
60
+ };
61
+ }
62
+ return {
63
+ success: false,
64
+ content: "",
65
+ error: `Failed to stop task ${taskId} (status: ${task.status})`,
66
+ };
67
+ }
68
+ },
69
+ formatCompactParams: (params: Record<string, unknown>) => {
70
+ return params.task_id as string;
71
+ },
72
+ };