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
@@ -26,6 +26,7 @@ import {
26
26
  SESSION_DIR,
27
27
  } from "../services/session.js";
28
28
  import { ChatCompletionMessageFunctionToolCall } from "openai/resources.js";
29
+ import type { MemoryRuleManager } from "./MemoryRuleManager.js";
29
30
  import { pathEncoder } from "../utils/pathEncoder.js";
30
31
 
31
32
  export interface MessageManagerCallbacks {
@@ -57,6 +58,7 @@ export interface MessageManagerCallbacks {
57
58
  onUpdateCommandOutputMessage?: (command: string, output: string) => void;
58
59
  onCompleteCommandMessage?: (command: string, exitCode: number) => void;
59
60
  onSlashCommandsChange?: (commands: SlashCommand[]) => void;
61
+ onInfoBlockAdded?: (content: string) => void;
60
62
  // Rewind callbacks
61
63
  onShowRewind?: () => void;
62
64
  // Subagent callbacks
@@ -80,6 +82,7 @@ export interface MessageManagerOptions {
80
82
  logger?: Logger;
81
83
  sessionType?: "main" | "subagent";
82
84
  subagentType?: string;
85
+ memoryRuleManager?: MemoryRuleManager;
83
86
  }
84
87
 
85
88
  export class MessageManager {
@@ -95,6 +98,7 @@ export class MessageManager {
95
98
  private transcriptPath: string; // Cached transcript path
96
99
  private savedMessageCount: number; // Track how many messages have been saved to prevent duplication
97
100
  private filesInContext: Set<string> = new Set(); // Track files mentioned in the conversation
101
+ private memoryRuleManager?: MemoryRuleManager;
98
102
  private sessionType: "main" | "subagent";
99
103
  private subagentType?: string;
100
104
 
@@ -110,6 +114,7 @@ export class MessageManager {
110
114
  this.savedMessageCount = 0; // Initialize saved message count tracker
111
115
  this.sessionType = options.sessionType || "main";
112
116
  this.subagentType = options.subagentType;
117
+ this.memoryRuleManager = options.memoryRuleManager;
113
118
 
114
119
  // Compute and cache the transcript path
115
120
  this.transcriptPath = this.computeTranscriptPath();
@@ -151,6 +156,30 @@ export class MessageManager {
151
156
  return this.transcriptPath;
152
157
  }
153
158
 
159
+ /**
160
+ * Get combined memory content (project memory + user memory + modular rules)
161
+ */
162
+ public async getCombinedMemory(): Promise<string> {
163
+ const memory = await import("../services/memory.js");
164
+ let combined = await memory.getCombinedMemoryContent(this.workdir);
165
+
166
+ if (this.memoryRuleManager) {
167
+ const filesInContext = this.getFilesInContext();
168
+ const activeRules = this.memoryRuleManager.getActiveRules(filesInContext);
169
+ if (activeRules.length > 0) {
170
+ this.logger?.debug(
171
+ `Active modular rules (${activeRules.length}): ${activeRules.map((r) => r.id).join(", ")}`,
172
+ );
173
+ if (combined) {
174
+ combined += "\n\n";
175
+ }
176
+ combined += activeRules.map((r) => r.content).join("\n\n");
177
+ }
178
+ }
179
+
180
+ return combined;
181
+ }
182
+
154
183
  /**
155
184
  * Compute the transcript path using cached encoded workdir
156
185
  * Called during construction and when sessionId changes
@@ -400,6 +429,18 @@ export class MessageManager {
400
429
  this.callbacks.onErrorBlockAdded?.(error);
401
430
  }
402
431
 
432
+ public addInfoBlock(content: string): void {
433
+ const lastMessage = this.messages[this.messages.length - 1];
434
+ if (lastMessage && lastMessage.role === "assistant") {
435
+ lastMessage.blocks.push({
436
+ type: "info",
437
+ content,
438
+ } as unknown as import("../types/index.js").MessageBlock);
439
+ this.setMessages([...this.messages]);
440
+ this.callbacks.onInfoBlockAdded?.(content);
441
+ }
442
+ }
443
+
403
444
  /**
404
445
  * Compress messages and update session, delete compressed messages, only keep compressed messages and subsequent messages
405
446
  */
@@ -494,12 +535,13 @@ export class MessageManager {
494
535
  subagentName: string,
495
536
  sessionId: string,
496
537
  configuration: SubagentConfiguration,
497
- status: "active" | "completed" | "error" = "active",
538
+ status: "active" | "completed" | "error" | "aborted" = "active",
498
539
  parameters: {
499
540
  description: string;
500
541
  prompt: string;
501
542
  subagent_type: string;
502
543
  },
544
+ runInBackground?: boolean,
503
545
  ): void {
504
546
  const params: AddSubagentBlockParams = {
505
547
  messages: this.messages,
@@ -508,6 +550,7 @@ export class MessageManager {
508
550
  sessionId,
509
551
  status,
510
552
  configuration,
553
+ runInBackground,
511
554
  };
512
555
  const updatedMessages = addSubagentBlockToMessage(params);
513
556
  this.setMessages(updatedMessages);
@@ -519,6 +562,7 @@ export class MessageManager {
519
562
  updates: Partial<{
520
563
  status: "active" | "completed" | "error" | "aborted";
521
564
  sessionId: string;
565
+ runInBackground: boolean;
522
566
  }>,
523
567
  ): void {
524
568
  const updatedMessages = updateSubagentBlockInMessage(
@@ -532,7 +576,9 @@ export class MessageManager {
532
576
  subagentId,
533
577
  status: updates.status || "active",
534
578
  };
535
- this.callbacks.onSubAgentBlockUpdated?.(params.subagentId, params.status);
579
+ if (updates.status) {
580
+ this.callbacks.onSubAgentBlockUpdated?.(params.subagentId, params.status);
581
+ }
536
582
  }
537
583
 
538
584
  /**
@@ -237,13 +237,7 @@ export class PermissionManager {
237
237
  * Get the current effective permission mode for tool execution context
238
238
  */
239
239
  getCurrentEffectiveMode(cliPermissionMode?: PermissionMode): PermissionMode {
240
- const mode = this.resolveEffectivePermissionMode(cliPermissionMode);
241
- this.logger?.debug("getCurrentEffectiveMode", {
242
- cliPermissionMode,
243
- configuredDefaultMode: this.configuredDefaultMode,
244
- resolvedMode: mode,
245
- });
246
- return mode;
240
+ return this.resolveEffectivePermissionMode(cliPermissionMode);
247
241
  }
248
242
 
249
243
  /**
@@ -149,9 +149,7 @@ export class PluginManager {
149
149
  * @param configs Array of plugin configurations
150
150
  */
151
151
  async loadPlugins(configs: PluginConfig[]): Promise<void> {
152
- // Load installed plugins from marketplace first
153
- await this.loadInstalledPlugins();
154
-
152
+ // Load plugins from configuration (e.g. --plugin-dir) first to give them higher priority
155
153
  for (const config of configs) {
156
154
  if (config.type !== "local") {
157
155
  this.logger?.warn(`Unsupported plugin type: ${config.type}`);
@@ -164,6 +162,9 @@ export class PluginManager {
164
162
 
165
163
  await this.loadSinglePlugin(absolutePath);
166
164
  }
165
+
166
+ // Load installed plugins from marketplace
167
+ await this.loadInstalledPlugins();
167
168
  }
168
169
 
169
170
  /**
@@ -1,5 +1,6 @@
1
1
  import type { MessageManager } from "./messageManager.js";
2
2
  import type { AIManager } from "./aiManager.js";
3
+ import type { BackgroundTaskManager } from "./backgroundTaskManager.js";
3
4
  import type {
4
5
  SlashCommand,
5
6
  CustomSlashCommand,
@@ -26,6 +27,7 @@ const execAsync = promisify(exec);
26
27
  export interface SlashCommandManagerOptions {
27
28
  messageManager: MessageManager;
28
29
  aiManager: AIManager;
30
+ backgroundTaskManager: BackgroundTaskManager;
29
31
  workdir: string;
30
32
  logger?: Logger;
31
33
  }
@@ -35,12 +37,14 @@ export class SlashCommandManager {
35
37
  private customCommands = new Map<string, CustomSlashCommand>();
36
38
  private messageManager: MessageManager;
37
39
  private aiManager: AIManager;
40
+ private backgroundTaskManager: BackgroundTaskManager;
38
41
  private workdir: string;
39
42
  private logger?: Logger;
40
43
 
41
44
  constructor(options: SlashCommandManagerOptions) {
42
45
  this.messageManager = options.messageManager;
43
46
  this.aiManager = options.aiManager;
47
+ this.backgroundTaskManager = options.backgroundTaskManager;
44
48
  this.workdir = options.workdir;
45
49
  this.logger = options.logger;
46
50
 
@@ -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,119 @@ 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
278
- this.updateInstanceStatus(instance.subagentId, "aborted");
279
- this.parentMessageManager.updateSubagentBlock(instance.subagentId, {
280
- status: "aborted",
281
- });
282
- },
283
- () => {
284
- // Abort the AI execution
285
- instance.aiManager.abortAIMessage();
286
- },
287
- ]);
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;
288
329
  }
289
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
+ this.updateInstanceStatus(instance.subagentId, "aborted");
386
+ this.parentMessageManager.updateSubagentBlock(instance.subagentId, {
387
+ status: "aborted",
388
+ });
389
+ },
390
+ () => {
391
+ // Abort the AI execution
392
+ instance.aiManager.abortAIMessage();
393
+ },
394
+ ]);
395
+ }
396
+
397
+ try {
290
398
  // Add the user's prompt as a message
291
399
  instance.messageManager.addUserMessage({ content: prompt });
292
400
 
@@ -326,21 +434,14 @@ export class SubagentManager {
326
434
  model: resolvedModel,
327
435
  });
328
436
 
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
- }
437
+ // If we have an abort signal, race against it using utilities to prevent listener accumulation
438
+ if (abortSignal) {
439
+ await Promise.race([
440
+ executeAI,
441
+ createAbortPromise(abortSignal, "Task was aborted"),
442
+ ]);
443
+ } else {
444
+ await executeAI;
344
445
  }
345
446
 
346
447
  // Get the latest messages to extract the response
@@ -365,13 +466,43 @@ export class SubagentManager {
365
466
  status: "completed",
366
467
  });
367
468
 
469
+ // If this was transitioned to background, update the background task
470
+ if (instance.backgroundTaskId && this.backgroundTaskManager) {
471
+ const task = this.backgroundTaskManager.getTask(
472
+ instance.backgroundTaskId,
473
+ );
474
+ if (task) {
475
+ task.status = "completed";
476
+ task.stdout = response || "Task completed with no text response";
477
+ task.endTime = Date.now();
478
+ if (task.startTime) {
479
+ task.runtime = task.endTime - task.startTime;
480
+ }
481
+ }
482
+ }
483
+
368
484
  return response || "Task completed with no text response";
369
485
  } catch (error) {
370
- this.updateInstanceStatus(instance.subagentId, "error");
371
- this.parentMessageManager.updateSubagentBlock(instance.subagentId, {
372
- status: "error",
373
- });
486
+ // If this was transitioned to background, update the background task with error
487
+ if (instance.backgroundTaskId && this.backgroundTaskManager) {
488
+ const task = this.backgroundTaskManager.getTask(
489
+ instance.backgroundTaskId,
490
+ );
491
+ if (task) {
492
+ task.status = "failed";
493
+ task.stderr = error instanceof Error ? error.message : String(error);
494
+ task.endTime = Date.now();
495
+ if (task.startTime) {
496
+ task.runtime = task.endTime - task.startTime;
497
+ }
498
+ }
499
+ }
374
500
  throw error;
501
+ } finally {
502
+ // Clean up abort listeners to prevent memory leaks
503
+ if (abortCleanup) {
504
+ abortCleanup();
505
+ }
375
506
  }
376
507
  }
377
508
 
@@ -462,6 +593,7 @@ export class SubagentManager {
462
593
  logger: this.logger,
463
594
  sessionType: "subagent",
464
595
  subagentType: configuration.name, // Use configuration name for restored sessions
596
+ memoryRuleManager: this.memoryRuleManager,
465
597
  });
466
598
 
467
599
  // 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
  /**