wave-agent-sdk 0.4.0 → 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 (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 +59 -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 +8 -3
  8. package/dist/managers/aiManager.d.ts.map +1 -1
  9. package/dist/managers/aiManager.js +35 -9
  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 +141 -35
  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 +52 -174
  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 +79 -45
  77. package/src/constants/tools.ts +2 -2
  78. package/src/managers/MemoryRuleManager.ts +1 -1
  79. package/src/managers/aiManager.ts +50 -17
  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 +171 -31
  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 +73 -200
  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 { randomUUID } from "crypto";
2
+ import type { MemoryRuleManager } from "./MemoryRuleManager.js";
2
3
  import type { SubagentConfiguration } from "../utils/subagentParser.js";
3
4
  import type {
4
5
  Message,
@@ -20,6 +21,7 @@ import {
20
21
  addConsolidatedAbortListener,
21
22
  createAbortPromise,
22
23
  } from "../utils/abortUtils.js";
24
+ import { BackgroundTaskManager } from "./backgroundTaskManager.js";
23
25
 
24
26
  export interface SubagentManagerCallbacks {
25
27
  // Granular subagent message callbacks (015-subagent-message-callbacks)
@@ -60,6 +62,7 @@ export interface SubagentInstance {
60
62
  status: "initializing" | "active" | "completed" | "error" | "aborted";
61
63
  messages: Message[];
62
64
  subagentType: string; // Store the subagent type for hook context
65
+ backgroundTaskId?: string; // ID of the background task if transitioned
63
66
  }
64
67
 
65
68
  export interface SubagentManagerOptions {
@@ -74,6 +77,8 @@ export interface SubagentManagerOptions {
74
77
  getLanguage: () => string | undefined;
75
78
  hookManager?: HookManager;
76
79
  onUsageAdded?: (usage: Usage) => void;
80
+ backgroundTaskManager?: BackgroundTaskManager;
81
+ memoryRuleManager?: MemoryRuleManager;
77
82
  }
78
83
 
79
84
  export class SubagentManager {
@@ -91,6 +96,8 @@ export class SubagentManager {
91
96
  private getLanguage: () => string | undefined;
92
97
  private hookManager?: HookManager;
93
98
  private onUsageAdded?: (usage: Usage) => void;
99
+ private backgroundTaskManager?: BackgroundTaskManager;
100
+ private memoryRuleManager?: MemoryRuleManager;
94
101
 
95
102
  constructor(options: SubagentManagerOptions) {
96
103
  this.workdir = options.workdir;
@@ -104,6 +111,8 @@ export class SubagentManager {
104
111
  this.getLanguage = options.getLanguage;
105
112
  this.hookManager = options.hookManager;
106
113
  this.onUsageAdded = options.onUsageAdded;
114
+ this.backgroundTaskManager = options.backgroundTaskManager;
115
+ this.memoryRuleManager = options.memoryRuleManager;
107
116
  }
108
117
 
109
118
  /**
@@ -158,6 +167,7 @@ export class SubagentManager {
158
167
  prompt: string;
159
168
  subagent_type: string;
160
169
  },
170
+ runInBackground?: boolean,
161
171
  ): Promise<SubagentInstance> {
162
172
  if (!this.parentToolManager) {
163
173
  throw new Error(
@@ -176,6 +186,7 @@ export class SubagentManager {
176
186
  logger: this.logger,
177
187
  sessionType: "subagent",
178
188
  subagentType: parameters.subagent_type,
189
+ memoryRuleManager: this.memoryRuleManager,
179
190
  });
180
191
 
181
192
  // Use the parent tool manager directly - tool restrictions will be handled by allowedTools parameter
@@ -241,6 +252,7 @@ export class SubagentManager {
241
252
  configuration,
242
253
  "active",
243
254
  parameters,
255
+ runInBackground,
244
256
  );
245
257
 
246
258
  return instance;
@@ -256,6 +268,7 @@ export class SubagentManager {
256
268
  instance: SubagentInstance,
257
269
  prompt: string,
258
270
  abortSignal?: AbortSignal,
271
+ runInBackground?: boolean,
259
272
  ): Promise<string> {
260
273
  try {
261
274
  // Check if already aborted before starting
@@ -269,24 +282,125 @@ export class SubagentManager {
269
282
  status: "active",
270
283
  });
271
284
 
272
- // Set up consolidated abort handler to prevent listener accumulation
273
- let abortCleanup: (() => void) | undefined;
274
- if (abortSignal) {
275
- abortCleanup = addConsolidatedAbortListener(abortSignal, [
276
- () => {
277
- // Update status to aborted
285
+ if (runInBackground && this.backgroundTaskManager) {
286
+ const taskId = this.backgroundTaskManager.generateId();
287
+ const startTime = Date.now();
288
+
289
+ this.backgroundTaskManager.addTask({
290
+ id: taskId,
291
+ type: "subagent",
292
+ status: "running",
293
+ startTime,
294
+ description: instance.configuration.description,
295
+ stdout: "",
296
+ stderr: "",
297
+ });
298
+
299
+ instance.backgroundTaskId = taskId;
300
+
301
+ // Execute in background
302
+ (async () => {
303
+ try {
304
+ const result = await this.internalExecute(
305
+ instance,
306
+ prompt,
307
+ abortSignal,
308
+ );
309
+ const task = this.backgroundTaskManager?.getTask(taskId);
310
+ if (task) {
311
+ task.status = "completed";
312
+ task.stdout = result;
313
+ task.endTime = Date.now();
314
+ task.runtime = task.endTime - startTime;
315
+ }
316
+ } catch (error) {
317
+ const task = this.backgroundTaskManager?.getTask(taskId);
318
+ if (task) {
319
+ task.status = "failed";
320
+ task.stderr =
321
+ error instanceof Error ? error.message : String(error);
322
+ task.endTime = Date.now();
323
+ task.runtime = task.endTime - startTime;
324
+ }
325
+ }
326
+ })();
327
+
328
+ return taskId;
329
+ }
330
+
331
+ return await this.internalExecute(instance, prompt, abortSignal);
332
+ } catch (error) {
333
+ this.updateInstanceStatus(instance.subagentId, "error");
334
+ this.parentMessageManager.updateSubagentBlock(instance.subagentId, {
335
+ status: "error",
336
+ });
337
+ throw error;
338
+ }
339
+ }
340
+
341
+ async backgroundInstance(subagentId: string): Promise<string> {
342
+ const instance = this.instances.get(subagentId);
343
+ if (!instance) {
344
+ throw new Error(`Subagent instance ${subagentId} not found`);
345
+ }
346
+
347
+ if (!this.backgroundTaskManager) {
348
+ throw new Error("BackgroundTaskManager not available");
349
+ }
350
+
351
+ const taskId = this.backgroundTaskManager.generateId();
352
+ const startTime = Date.now();
353
+
354
+ this.backgroundTaskManager.addTask({
355
+ id: taskId,
356
+ type: "subagent",
357
+ status: "running",
358
+ startTime,
359
+ description: instance.configuration.description,
360
+ stdout: "",
361
+ stderr: "",
362
+ });
363
+
364
+ instance.backgroundTaskId = taskId;
365
+
366
+ // Update parent message manager to reflect background status
367
+ this.parentMessageManager.updateSubagentBlock(subagentId, {
368
+ runInBackground: true,
369
+ });
370
+
371
+ return taskId;
372
+ }
373
+
374
+ private async internalExecute(
375
+ instance: SubagentInstance,
376
+ prompt: string,
377
+ abortSignal?: AbortSignal,
378
+ ): Promise<string> {
379
+ // Set up consolidated abort handler to prevent listener accumulation
380
+ let abortCleanup: (() => void) | undefined;
381
+ if (abortSignal) {
382
+ abortCleanup = addConsolidatedAbortListener(abortSignal, [
383
+ () => {
384
+ // Update status to aborted
385
+ // Only update status if it's NOT a background task
386
+ if (!instance.backgroundTaskId) {
278
387
  this.updateInstanceStatus(instance.subagentId, "aborted");
279
388
  this.parentMessageManager.updateSubagentBlock(instance.subagentId, {
280
389
  status: "aborted",
281
390
  });
282
- },
283
- () => {
284
- // Abort the AI execution
391
+ }
392
+ },
393
+ () => {
394
+ // Abort the AI execution
395
+ // Only abort if it's NOT a background task
396
+ if (!instance.backgroundTaskId) {
285
397
  instance.aiManager.abortAIMessage();
286
- },
287
- ]);
288
- }
398
+ }
399
+ },
400
+ ]);
401
+ }
289
402
 
403
+ try {
290
404
  // Add the user's prompt as a message
291
405
  instance.messageManager.addUserMessage({ content: prompt });
292
406
 
@@ -326,21 +440,16 @@ export class SubagentManager {
326
440
  model: resolvedModel,
327
441
  });
328
442
 
329
- try {
330
- // If we have an abort signal, race against it using utilities to prevent listener accumulation
331
- if (abortSignal) {
332
- await Promise.race([
333
- executeAI,
334
- createAbortPromise(abortSignal, "Task was aborted"),
335
- ]);
336
- } else {
337
- await executeAI;
338
- }
339
- } finally {
340
- // Clean up abort listeners to prevent memory leaks
341
- if (abortCleanup) {
342
- abortCleanup();
343
- }
443
+ // If we have an abort signal, race against it using utilities to prevent listener accumulation
444
+ // BUT: If this is a background task, we DON'T want to race against the abort signal
445
+ // because the abort signal (Esc) should only stop the tool watching the task, not the task itself.
446
+ if (abortSignal && !instance.backgroundTaskId) {
447
+ await Promise.race([
448
+ executeAI,
449
+ createAbortPromise(abortSignal, "Task was aborted"),
450
+ ]);
451
+ } else {
452
+ await executeAI;
344
453
  }
345
454
 
346
455
  // Get the latest messages to extract the response
@@ -365,13 +474,43 @@ export class SubagentManager {
365
474
  status: "completed",
366
475
  });
367
476
 
477
+ // If this was transitioned to background, update the background task
478
+ if (instance.backgroundTaskId && this.backgroundTaskManager) {
479
+ const task = this.backgroundTaskManager.getTask(
480
+ instance.backgroundTaskId,
481
+ );
482
+ if (task) {
483
+ task.status = "completed";
484
+ task.stdout = response || "Task completed with no text response";
485
+ task.endTime = Date.now();
486
+ if (task.startTime) {
487
+ task.runtime = task.endTime - task.startTime;
488
+ }
489
+ }
490
+ }
491
+
368
492
  return response || "Task completed with no text response";
369
493
  } catch (error) {
370
- this.updateInstanceStatus(instance.subagentId, "error");
371
- this.parentMessageManager.updateSubagentBlock(instance.subagentId, {
372
- status: "error",
373
- });
494
+ // If this was transitioned to background, update the background task with error
495
+ if (instance.backgroundTaskId && this.backgroundTaskManager) {
496
+ const task = this.backgroundTaskManager.getTask(
497
+ instance.backgroundTaskId,
498
+ );
499
+ if (task) {
500
+ task.status = "failed";
501
+ task.stderr = error instanceof Error ? error.message : String(error);
502
+ task.endTime = Date.now();
503
+ if (task.startTime) {
504
+ task.runtime = task.endTime - task.startTime;
505
+ }
506
+ }
507
+ }
374
508
  throw error;
509
+ } finally {
510
+ // Clean up abort listeners to prevent memory leaks
511
+ if (abortCleanup) {
512
+ abortCleanup();
513
+ }
375
514
  }
376
515
  }
377
516
 
@@ -462,6 +601,7 @@ export class SubagentManager {
462
601
  logger: this.logger,
463
602
  sessionType: "subagent",
464
603
  subagentType: configuration.name, // Use configuration name for restored sessions
604
+ memoryRuleManager: this.memoryRuleManager,
465
605
  });
466
606
 
467
607
  // Use the parent tool manager
@@ -1,5 +1,7 @@
1
1
  import type { ToolContext, ToolPlugin, ToolResult } from "../tools/types.js";
2
- import { bashTool, bashOutputTool, killBashTool } from "../tools/bashTool.js";
2
+ import { bashTool } from "../tools/bashTool.js";
3
+ import { taskOutputTool } from "../tools/taskOutputTool.js";
4
+ import { taskStopTool } from "../tools/taskStopTool.js";
3
5
  import { deleteFileTool } from "../tools/deleteFileTool.js";
4
6
  import { editTool } from "../tools/editTool.js";
5
7
  import { multiEditTool } from "../tools/multiEditTool.js";
@@ -33,10 +35,14 @@ export interface ToolManagerOptions {
33
35
  mcpManager: McpManager;
34
36
  lspManager?: ILspManager;
35
37
  logger?: Logger;
36
- /** Optional permission manager for handling tool permission checks */
38
+ /** Permission manager for handling tool permission checks */
37
39
  permissionManager?: PermissionManager;
40
+ /** Foreground task manager for backgrounding tasks */
41
+ foregroundTaskManager?: import("../types/processes.js").IForegroundTaskManager;
38
42
  /** Reversion manager for file snapshots */
39
43
  reversionManager?: ReversionManager;
44
+ /** Background task manager for background execution */
45
+ backgroundTaskManager?: import("./backgroundTaskManager.js").BackgroundTaskManager;
40
46
  /** Permission mode for tool execution (defaults to "default") */
41
47
  permissionMode?: PermissionMode;
42
48
  /** Custom permission callback for tool usage */
@@ -55,7 +61,9 @@ class ToolManager {
55
61
  private lspManager?: ILspManager;
56
62
  private logger?: Logger;
57
63
  private permissionManager?: PermissionManager;
64
+ private foregroundTaskManager?: import("../types/processes.js").IForegroundTaskManager;
58
65
  private reversionManager?: ReversionManager;
66
+ private backgroundTaskManager?: import("./backgroundTaskManager.js").BackgroundTaskManager;
59
67
  private permissionMode?: PermissionMode;
60
68
  private canUseToolCallback?: PermissionCallback;
61
69
 
@@ -64,7 +72,9 @@ class ToolManager {
64
72
  this.lspManager = options.lspManager;
65
73
  this.logger = options.logger;
66
74
  this.permissionManager = options.permissionManager;
75
+ this.foregroundTaskManager = options.foregroundTaskManager;
67
76
  this.reversionManager = options.reversionManager;
77
+ this.backgroundTaskManager = options.backgroundTaskManager;
68
78
  // Store CLI permission mode, let PermissionManager resolve effective mode
69
79
  this.permissionMode = options.permissionMode;
70
80
  this.canUseToolCallback = options.canUseToolCallback;
@@ -106,8 +116,8 @@ class ToolManager {
106
116
  }): void {
107
117
  const builtInTools = [
108
118
  bashTool,
109
- bashOutputTool,
110
- killBashTool,
119
+ taskOutputTool,
120
+ taskStopTool,
111
121
  deleteFileTool,
112
122
  editTool,
113
123
  multiEditTool,
@@ -168,6 +178,8 @@ class ToolManager {
168
178
  canUseToolCallback: this.canUseToolCallback,
169
179
  permissionManager: this.permissionManager,
170
180
  reversionManager: this.reversionManager,
181
+ backgroundTaskManager: this.backgroundTaskManager,
182
+ foregroundTaskManager: this.foregroundTaskManager,
171
183
  mcpManager: this.mcpManager,
172
184
  lspManager: this.lspManager,
173
185
  };
@@ -43,7 +43,9 @@ export class GitService {
43
43
 
44
44
  try {
45
45
  const refArgs = ref ? `-b "${ref}"` : "--depth 1";
46
- await execAsync(`LC_ALL=C git clone ${refArgs} "${url}" "${targetPath}"`);
46
+ await execAsync(`git clone ${refArgs} "${url}" "${targetPath}"`, {
47
+ env: { ...process.env, LC_ALL: "C" },
48
+ });
47
49
  } catch (error) {
48
50
  throw this.handleGitError(urlOrRepo, error);
49
51
  }
@@ -59,7 +61,9 @@ export class GitService {
59
61
  );
60
62
  }
61
63
  try {
62
- await execAsync(`LC_ALL=C git -C "${targetPath}" pull`);
64
+ await execAsync(`git -C "${targetPath}" pull`, {
65
+ env: { ...process.env, LC_ALL: "C" },
66
+ });
63
67
  } catch (error) {
64
68
  throw this.handleGitError(targetPath, error);
65
69
  }
@@ -346,7 +346,10 @@ export class MarketplaceService {
346
346
  /**
347
347
  * Installs a plugin from a marketplace
348
348
  */
349
- async installPlugin(pluginAtMarketplace: string): Promise<InstalledPlugin> {
349
+ async installPlugin(
350
+ pluginAtMarketplace: string,
351
+ projectPath?: string,
352
+ ): Promise<InstalledPlugin> {
350
353
  const [pluginName, marketplaceName] = pluginAtMarketplace.split("@");
351
354
  if (!pluginName || !marketplaceName) {
352
355
  throw new Error("Invalid plugin format. Use name@marketplace");
@@ -432,7 +435,10 @@ export class MarketplaceService {
432
435
 
433
436
  const installedRegistry = await this.getInstalledPlugins();
434
437
  const existingIndex = installedRegistry.plugins.findIndex(
435
- (p) => p.name === pluginName && p.marketplace === marketplaceName,
438
+ (p) =>
439
+ p.name === pluginName &&
440
+ p.marketplace === marketplaceName &&
441
+ p.projectPath === projectPath,
436
442
  );
437
443
 
438
444
  const installedPlugin: InstalledPlugin = {
@@ -440,6 +446,7 @@ export class MarketplaceService {
440
446
  marketplace: marketplaceName,
441
447
  version,
442
448
  cachePath,
449
+ projectPath,
443
450
  };
444
451
 
445
452
  if (existingIndex >= 0) {
@@ -467,7 +474,10 @@ export class MarketplaceService {
467
474
  /**
468
475
  * Uninstalls a plugin
469
476
  */
470
- async uninstallPlugin(pluginAtMarketplace: string): Promise<void> {
477
+ async uninstallPlugin(
478
+ pluginAtMarketplace: string,
479
+ projectPath?: string,
480
+ ): Promise<void> {
471
481
  const [pluginName, marketplaceName] = pluginAtMarketplace.split("@");
472
482
  if (!pluginName || !marketplaceName) {
473
483
  throw new Error("Invalid plugin format. Use name@marketplace");
@@ -475,25 +485,33 @@ export class MarketplaceService {
475
485
 
476
486
  const installedRegistry = await this.getInstalledPlugins();
477
487
  const pluginIndex = installedRegistry.plugins.findIndex(
478
- (p) => p.name === pluginName && p.marketplace === marketplaceName,
488
+ (p) =>
489
+ p.name === pluginName &&
490
+ p.marketplace === marketplaceName &&
491
+ p.projectPath === projectPath,
479
492
  );
480
493
 
481
494
  if (pluginIndex === -1) {
482
495
  throw new Error(
483
- `Plugin ${pluginName}@${marketplaceName} is not installed`,
496
+ `Plugin ${pluginName}@${marketplaceName} is not installed${projectPath ? ` for project ${projectPath}` : ""}`,
484
497
  );
485
498
  }
486
499
 
487
- const plugin = installedRegistry.plugins[pluginIndex];
488
-
489
- // Remove cached files
490
- if (existsSync(plugin.cachePath)) {
491
- await fs.rm(plugin.cachePath, { recursive: true, force: true });
492
- }
500
+ const pluginToRemove = installedRegistry.plugins[pluginIndex];
493
501
 
494
- // Remove from registry
502
+ // Remove from registry first
495
503
  installedRegistry.plugins.splice(pluginIndex, 1);
496
504
  await this.saveInstalledPlugins(installedRegistry);
505
+
506
+ // Check if any other project is still using this same cache path
507
+ const isStillReferenced = installedRegistry.plugins.some(
508
+ (p) => p.cachePath === pluginToRemove.cachePath,
509
+ );
510
+
511
+ // Only remove cached files if no other references exist
512
+ if (!isStillReferenced && existsSync(pluginToRemove.cachePath)) {
513
+ await fs.rm(pluginToRemove.cachePath, { recursive: true, force: true });
514
+ }
497
515
  }
498
516
 
499
517
  /**
@@ -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