wave-agent-sdk 0.2.1 → 0.5.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 (194) hide show
  1. package/dist/agent.d.ts +66 -20
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +156 -83
  4. package/dist/constants/prompts.d.ts +7 -2
  5. package/dist/constants/prompts.d.ts.map +1 -1
  6. package/dist/constants/prompts.js +41 -5
  7. package/dist/constants/tools.d.ts +2 -2
  8. package/dist/constants/tools.js +2 -2
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +1 -1
  12. package/dist/managers/MemoryRuleManager.d.ts.map +1 -1
  13. package/dist/managers/MemoryRuleManager.js +16 -2
  14. package/dist/managers/aiManager.d.ts +14 -4
  15. package/dist/managers/aiManager.d.ts.map +1 -1
  16. package/dist/managers/aiManager.js +61 -9
  17. package/dist/managers/backgroundBashManager.d.ts.map +1 -1
  18. package/dist/managers/backgroundBashManager.js +1 -0
  19. package/dist/managers/backgroundTaskManager.d.ts +35 -0
  20. package/dist/managers/backgroundTaskManager.d.ts.map +1 -0
  21. package/dist/managers/backgroundTaskManager.js +249 -0
  22. package/dist/managers/bashManager.d.ts.map +1 -1
  23. package/dist/managers/bashManager.js +0 -3
  24. package/dist/managers/foregroundTaskManager.d.ts +9 -0
  25. package/dist/managers/foregroundTaskManager.d.ts.map +1 -0
  26. package/dist/managers/foregroundTaskManager.js +20 -0
  27. package/dist/managers/liveConfigManager.d.ts +1 -1
  28. package/dist/managers/liveConfigManager.d.ts.map +1 -1
  29. package/dist/managers/lspManager.d.ts.map +1 -1
  30. package/dist/managers/lspManager.js +3 -1
  31. package/dist/managers/messageManager.d.ts +34 -4
  32. package/dist/managers/messageManager.d.ts.map +1 -1
  33. package/dist/managers/messageManager.js +104 -13
  34. package/dist/managers/permissionManager.d.ts.map +1 -1
  35. package/dist/managers/permissionManager.js +11 -13
  36. package/dist/managers/pluginManager.d.ts.map +1 -1
  37. package/dist/managers/pluginManager.js +3 -2
  38. package/dist/managers/pluginScopeManager.d.ts +13 -2
  39. package/dist/managers/pluginScopeManager.d.ts.map +1 -1
  40. package/dist/managers/pluginScopeManager.js +38 -0
  41. package/dist/managers/reversionManager.d.ts +39 -0
  42. package/dist/managers/reversionManager.d.ts.map +1 -0
  43. package/dist/managers/reversionManager.js +118 -0
  44. package/dist/managers/slashCommandManager.d.ts +4 -1
  45. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  46. package/dist/managers/slashCommandManager.js +16 -6
  47. package/dist/managers/subagentManager.d.ts +13 -2
  48. package/dist/managers/subagentManager.d.ts.map +1 -1
  49. package/dist/managers/subagentManager.js +144 -35
  50. package/dist/managers/toolManager.d.ts +11 -1
  51. package/dist/managers/toolManager.d.ts.map +1 -1
  52. package/dist/managers/toolManager.js +11 -3
  53. package/dist/services/GitService.d.ts.map +1 -1
  54. package/dist/services/GitService.js +6 -2
  55. package/dist/services/MarketplaceService.d.ts +14 -1
  56. package/dist/services/MarketplaceService.d.ts.map +1 -1
  57. package/dist/services/MarketplaceService.js +72 -4
  58. package/dist/services/MemoryRuleService.d.ts +1 -1
  59. package/dist/services/MemoryRuleService.d.ts.map +1 -1
  60. package/dist/services/MemoryRuleService.js +13 -2
  61. package/dist/services/aiService.js +1 -1
  62. package/dist/services/configurationService.d.ts +18 -2
  63. package/dist/services/configurationService.d.ts.map +1 -1
  64. package/dist/services/configurationService.js +62 -0
  65. package/dist/services/fileWatcher.d.ts +0 -5
  66. package/dist/services/fileWatcher.d.ts.map +1 -1
  67. package/dist/services/fileWatcher.js +0 -11
  68. package/dist/services/memory.js +1 -1
  69. package/dist/services/pluginLoader.d.ts.map +1 -1
  70. package/dist/services/pluginLoader.js +6 -1
  71. package/dist/services/reversionService.d.ts +24 -0
  72. package/dist/services/reversionService.d.ts.map +1 -0
  73. package/dist/services/reversionService.js +76 -0
  74. package/dist/services/session.d.ts +7 -0
  75. package/dist/services/session.d.ts.map +1 -1
  76. package/dist/services/session.js +126 -3
  77. package/dist/tools/bashTool.d.ts +0 -8
  78. package/dist/tools/bashTool.d.ts.map +1 -1
  79. package/dist/tools/bashTool.js +52 -174
  80. package/dist/tools/deleteFileTool.d.ts.map +1 -1
  81. package/dist/tools/deleteFileTool.js +9 -0
  82. package/dist/tools/editTool.d.ts.map +1 -1
  83. package/dist/tools/editTool.js +15 -4
  84. package/dist/tools/multiEditTool.d.ts.map +1 -1
  85. package/dist/tools/multiEditTool.js +16 -5
  86. package/dist/tools/taskOutputTool.d.ts +3 -0
  87. package/dist/tools/taskOutputTool.d.ts.map +1 -0
  88. package/dist/tools/taskOutputTool.js +149 -0
  89. package/dist/tools/taskStopTool.d.ts +3 -0
  90. package/dist/tools/taskStopTool.d.ts.map +1 -0
  91. package/dist/tools/taskStopTool.js +65 -0
  92. package/dist/tools/taskTool.d.ts.map +1 -1
  93. package/dist/tools/taskTool.js +105 -63
  94. package/dist/tools/types.d.ts +7 -0
  95. package/dist/tools/types.d.ts.map +1 -1
  96. package/dist/tools/writeTool.d.ts.map +1 -1
  97. package/dist/tools/writeTool.js +9 -0
  98. package/dist/types/commands.d.ts +1 -0
  99. package/dist/types/commands.d.ts.map +1 -1
  100. package/dist/types/configuration.d.ts +3 -0
  101. package/dist/types/configuration.d.ts.map +1 -1
  102. package/dist/types/environment.d.ts +2 -1
  103. package/dist/types/environment.d.ts.map +1 -1
  104. package/dist/types/environment.js +0 -6
  105. package/dist/types/history.d.ts +5 -0
  106. package/dist/types/history.d.ts.map +1 -0
  107. package/dist/types/history.js +1 -0
  108. package/dist/types/index.d.ts +1 -0
  109. package/dist/types/index.d.ts.map +1 -1
  110. package/dist/types/index.js +1 -0
  111. package/dist/types/marketplace.d.ts +4 -0
  112. package/dist/types/marketplace.d.ts.map +1 -1
  113. package/dist/types/messaging.d.ts +7 -1
  114. package/dist/types/messaging.d.ts.map +1 -1
  115. package/dist/types/processes.d.ts +24 -4
  116. package/dist/types/processes.d.ts.map +1 -1
  117. package/dist/types/reversion.d.ts +29 -0
  118. package/dist/types/reversion.d.ts.map +1 -0
  119. package/dist/types/reversion.js +1 -0
  120. package/dist/utils/builtinSubagents.d.ts.map +1 -1
  121. package/dist/utils/builtinSubagents.js +16 -0
  122. package/dist/utils/constants.d.ts +2 -2
  123. package/dist/utils/constants.d.ts.map +1 -1
  124. package/dist/utils/constants.js +2 -2
  125. package/dist/utils/editUtils.d.ts +4 -9
  126. package/dist/utils/editUtils.d.ts.map +1 -1
  127. package/dist/utils/editUtils.js +54 -55
  128. package/dist/utils/messageOperations.d.ts +3 -1
  129. package/dist/utils/messageOperations.d.ts.map +1 -1
  130. package/dist/utils/messageOperations.js +8 -1
  131. package/dist/utils/openaiClient.d.ts.map +1 -1
  132. package/dist/utils/openaiClient.js +56 -26
  133. package/dist/utils/promptHistory.d.ts +20 -0
  134. package/dist/utils/promptHistory.d.ts.map +1 -0
  135. package/dist/utils/promptHistory.js +117 -0
  136. package/package.json +5 -3
  137. package/src/agent.ts +193 -109
  138. package/src/constants/prompts.ts +45 -5
  139. package/src/constants/tools.ts +2 -2
  140. package/src/index.ts +1 -1
  141. package/src/managers/MemoryRuleManager.ts +18 -2
  142. package/src/managers/aiManager.ts +87 -18
  143. package/src/managers/backgroundBashManager.ts +1 -0
  144. package/src/managers/backgroundTaskManager.ts +306 -0
  145. package/src/managers/bashManager.ts +0 -4
  146. package/src/managers/foregroundTaskManager.ts +26 -0
  147. package/src/managers/liveConfigManager.ts +2 -1
  148. package/src/managers/lspManager.ts +3 -1
  149. package/src/managers/messageManager.ts +136 -18
  150. package/src/managers/permissionManager.ts +11 -13
  151. package/src/managers/pluginManager.ts +4 -3
  152. package/src/managers/pluginScopeManager.ts +57 -8
  153. package/src/managers/reversionManager.ts +152 -0
  154. package/src/managers/slashCommandManager.ts +30 -7
  155. package/src/managers/subagentManager.ts +176 -31
  156. package/src/managers/toolManager.ts +23 -4
  157. package/src/services/GitService.ts +6 -2
  158. package/src/services/MarketplaceService.ts +100 -4
  159. package/src/services/MemoryRuleService.ts +18 -6
  160. package/src/services/aiService.ts +1 -1
  161. package/src/services/configurationService.ts +79 -1
  162. package/src/services/fileWatcher.ts +0 -13
  163. package/src/services/memory.ts +1 -1
  164. package/src/services/pluginLoader.ts +7 -1
  165. package/src/services/reversionService.ts +94 -0
  166. package/src/services/session.ts +161 -3
  167. package/src/tools/bashTool.ts +73 -200
  168. package/src/tools/deleteFileTool.ts +15 -0
  169. package/src/tools/editTool.ts +20 -10
  170. package/src/tools/multiEditTool.ts +21 -11
  171. package/src/tools/taskOutputTool.ts +174 -0
  172. package/src/tools/taskStopTool.ts +72 -0
  173. package/src/tools/taskTool.ts +130 -74
  174. package/src/tools/types.ts +7 -0
  175. package/src/tools/writeTool.ts +14 -0
  176. package/src/types/commands.ts +3 -0
  177. package/src/types/configuration.ts +4 -0
  178. package/src/types/environment.ts +3 -1
  179. package/src/types/history.ts +4 -0
  180. package/src/types/index.ts +1 -0
  181. package/src/types/marketplace.ts +5 -0
  182. package/src/types/messaging.ts +9 -1
  183. package/src/types/processes.ts +33 -4
  184. package/src/types/reversion.ts +29 -0
  185. package/src/utils/builtinSubagents.ts +18 -0
  186. package/src/utils/constants.ts +2 -2
  187. package/src/utils/editUtils.ts +66 -58
  188. package/src/utils/messageOperations.ts +10 -0
  189. package/src/utils/openaiClient.ts +69 -35
  190. package/src/utils/promptHistory.ts +133 -0
  191. package/dist/utils/bashHistory.d.ts +0 -50
  192. package/dist/utils/bashHistory.d.ts.map +0 -1
  193. package/dist/utils/bashHistory.js +0 -256
  194. package/src/utils/bashHistory.ts +0 -320
@@ -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;
@@ -257,29 +291,45 @@ Usage notes:
257
291
  // Handle abort signal from context
258
292
  if (context?.abortSignal) {
259
293
  if (context.abortSignal.aborted) {
260
- handleAbort();
294
+ if (!isBackgrounded) {
295
+ handleAbort();
296
+ }
261
297
  return;
262
298
  }
263
299
  // Use { once: true } to prevent listener accumulation on signal reuse
264
- context.abortSignal.addEventListener("abort", () => handleAbort(), {
265
- once: true,
266
- });
300
+ context.abortSignal.addEventListener(
301
+ "abort",
302
+ () => {
303
+ if (!isBackgrounded) {
304
+ handleAbort();
305
+ }
306
+ },
307
+ {
308
+ once: true,
309
+ },
310
+ );
267
311
  }
268
312
 
269
313
  child.stdout?.on("data", (data) => {
270
- if (!isAborted) {
314
+ if (!isAborted && !isBackgrounded) {
271
315
  outputBuffer += stripAnsiColors(data.toString());
272
316
  }
273
317
  });
274
318
 
275
319
  child.stderr?.on("data", (data) => {
276
- if (!isAborted) {
320
+ if (!isAborted && !isBackgrounded) {
277
321
  errorBuffer += stripAnsiColors(data.toString());
278
322
  }
279
323
  });
280
324
 
281
325
  child.on("exit", (code) => {
282
- if (!isAborted) {
326
+ if (context.foregroundTaskManager) {
327
+ context.foregroundTaskManager.unregisterForegroundTask(
328
+ foregroundTaskId,
329
+ );
330
+ }
331
+
332
+ if (!isAborted && !isBackgrounded) {
283
333
  if (timeoutHandle) {
284
334
  clearTimeout(timeoutHandle);
285
335
  }
@@ -309,7 +359,13 @@ Usage notes:
309
359
  });
310
360
 
311
361
  child.on("error", (error) => {
312
- if (!isAborted) {
362
+ if (context.foregroundTaskManager) {
363
+ context.foregroundTaskManager.unregisterForegroundTask(
364
+ foregroundTaskId,
365
+ );
366
+ }
367
+
368
+ if (!isAborted && !isBackgrounded) {
313
369
  if (timeoutHandle) {
314
370
  clearTimeout(timeoutHandle);
315
371
  }
@@ -334,186 +390,3 @@ Usage notes:
334
390
  return `${command}${runInBackground ? " (background)" : ""}`;
335
391
  },
336
392
  };
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
- };
@@ -76,9 +76,24 @@ export const deleteFileTool: ToolPlugin = {
76
76
  }
77
77
  }
78
78
 
79
+ // Record snapshot for reversion
80
+ let snapshotId: string | undefined;
81
+ if (context.reversionManager && context.messageId) {
82
+ snapshotId = await context.reversionManager.recordSnapshot(
83
+ context.messageId,
84
+ filePath,
85
+ "delete",
86
+ );
87
+ }
88
+
79
89
  // Delete file
80
90
  await unlink(filePath);
81
91
 
92
+ // Commit snapshot on success
93
+ if (context.reversionManager && snapshotId) {
94
+ await context.reversionManager.commitSnapshot(snapshotId);
95
+ }
96
+
82
97
  logger.debug(`Successfully deleted file: ${filePath}`);
83
98
 
84
99
  return {
@@ -2,10 +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
- } from "../utils/editUtils.js";
5
+ import { escapeRegExp, analyzeEditMismatch } from "../utils/editUtils.js";
9
6
  import { EDIT_TOOL_NAME, READ_TOOL_NAME } from "../constants/tools.js";
10
7
 
11
8
  /**
@@ -114,17 +111,16 @@ export const editTool: ToolPlugin = {
114
111
  };
115
112
  }
116
113
 
117
- // Check if old_string exists (with smart indentation matching)
118
- const matchedOldString = findIndentationInsensitiveMatch(
119
- originalContent,
120
- oldString,
121
- );
114
+ // Check if old_string exists
115
+ const matchedOldString = originalContent.includes(oldString)
116
+ ? oldString
117
+ : null;
122
118
 
123
119
  if (!matchedOldString) {
124
120
  return {
125
121
  success: false,
126
122
  content: "",
127
- error: `old_string not found in file`,
123
+ error: analyzeEditMismatch(originalContent, oldString),
128
124
  };
129
125
  }
130
126
 
@@ -184,9 +180,23 @@ export const editTool: ToolPlugin = {
184
180
  }
185
181
  }
186
182
 
183
+ // Record snapshot for reversion
184
+ let snapshotId: string | undefined;
185
+ if (context.reversionManager && context.messageId) {
186
+ snapshotId = await context.reversionManager.recordSnapshot(
187
+ context.messageId,
188
+ resolvedPath,
189
+ "modify",
190
+ );
191
+ }
192
+
187
193
  // Write file
188
194
  try {
189
195
  await writeFile(resolvedPath, newContent, "utf-8");
196
+ // Commit snapshot on success
197
+ if (context.reversionManager && snapshotId) {
198
+ await context.reversionManager.commitSnapshot(snapshotId);
199
+ }
190
200
  } catch (writeError) {
191
201
  return {
192
202
  success: false,
@@ -2,10 +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
- } from "../utils/editUtils.js";
5
+ import { escapeRegExp, analyzeEditMismatch } from "../utils/editUtils.js";
9
6
  import {
10
7
  MULTI_EDIT_TOOL_NAME,
11
8
  EDIT_TOOL_NAME,
@@ -184,17 +181,16 @@ export const multiEditTool: ToolPlugin = {
184
181
  continue;
185
182
  }
186
183
 
187
- // Check if old_string exists (with smart indentation matching)
188
- const matchedOldString = findIndentationInsensitiveMatch(
189
- currentContent,
190
- edit.old_string,
191
- );
184
+ // Check if old_string exists
185
+ const matchedOldString = currentContent.includes(edit.old_string)
186
+ ? edit.old_string
187
+ : null;
192
188
 
193
189
  if (!matchedOldString) {
194
190
  return {
195
191
  success: false,
196
192
  content: "",
197
- error: `Edit operation ${i + 1}: old_string not found in current content`,
193
+ error: `Edit operation ${i + 1}: ${analyzeEditMismatch(currentContent, edit.old_string)}`,
198
194
  };
199
195
  }
200
196
 
@@ -202,7 +198,7 @@ export const multiEditTool: ToolPlugin = {
202
198
 
203
199
  if (replaceAll) {
204
200
  // Replace all matches
205
- const regex = new RegExp(escapeRegExp(matchedOldString), "g");
201
+ const regex = new RegExp(escapeRegExp(edit.old_string), "g");
206
202
  currentContent = currentContent.replace(regex, edit.new_string);
207
203
  replacementCount = (currentContent.match(regex) || []).length;
208
204
  appliedEdits.push(
@@ -257,9 +253,23 @@ export const multiEditTool: ToolPlugin = {
257
253
  }
258
254
  }
259
255
 
256
+ // Record snapshot for reversion
257
+ let snapshotId: string | undefined;
258
+ if (context.reversionManager && context.messageId) {
259
+ snapshotId = await context.reversionManager.recordSnapshot(
260
+ context.messageId,
261
+ resolvedPath,
262
+ isNewFile ? "create" : "modify",
263
+ );
264
+ }
265
+
260
266
  // Write file
261
267
  try {
262
268
  await writeFile(resolvedPath, currentContent, "utf-8");
269
+ // Commit snapshot on success
270
+ if (context.reversionManager && snapshotId) {
271
+ await context.reversionManager.commitSnapshot(snapshotId);
272
+ }
263
273
  } catch (writeError) {
264
274
  return {
265
275
  success: false,
@@ -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
+ };