wave-agent-sdk 0.5.0 → 0.6.2

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 (169) hide show
  1. package/dist/agent.d.ts +14 -11
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +64 -151
  4. package/dist/constants/subagents.d.ts +5 -0
  5. package/dist/constants/subagents.d.ts.map +1 -0
  6. package/dist/constants/subagents.js +4 -0
  7. package/dist/constants/tools.d.ts +4 -1
  8. package/dist/constants/tools.d.ts.map +1 -1
  9. package/dist/constants/tools.js +4 -1
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +1 -0
  13. package/dist/managers/aiManager.d.ts +2 -5
  14. package/dist/managers/aiManager.d.ts.map +1 -1
  15. package/dist/managers/aiManager.js +43 -48
  16. package/dist/managers/backgroundTaskManager.d.ts.map +1 -1
  17. package/dist/managers/backgroundTaskManager.js +63 -53
  18. package/dist/managers/foregroundTaskManager.d.ts.map +1 -1
  19. package/dist/managers/foregroundTaskManager.js +3 -2
  20. package/dist/managers/mcpManager.d.ts.map +1 -1
  21. package/dist/managers/messageManager.d.ts +13 -27
  22. package/dist/managers/messageManager.d.ts.map +1 -1
  23. package/dist/managers/messageManager.js +94 -89
  24. package/dist/managers/permissionManager.d.ts.map +1 -1
  25. package/dist/managers/permissionManager.js +25 -15
  26. package/dist/managers/planManager.d.ts +1 -1
  27. package/dist/managers/planManager.d.ts.map +1 -1
  28. package/dist/managers/planManager.js +2 -2
  29. package/dist/managers/reversionManager.d.ts.map +1 -1
  30. package/dist/managers/reversionManager.js +23 -2
  31. package/dist/managers/slashCommandManager.d.ts +3 -0
  32. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  33. package/dist/managers/slashCommandManager.js +8 -3
  34. package/dist/managers/subagentManager.d.ts +8 -14
  35. package/dist/managers/subagentManager.d.ts.map +1 -1
  36. package/dist/managers/subagentManager.js +46 -112
  37. package/dist/managers/toolManager.d.ts +11 -0
  38. package/dist/managers/toolManager.d.ts.map +1 -1
  39. package/dist/managers/toolManager.js +20 -2
  40. package/dist/{constants/prompts.d.ts → prompts/index.d.ts} +17 -15
  41. package/dist/prompts/index.d.ts.map +1 -0
  42. package/dist/prompts/index.js +309 -0
  43. package/dist/services/aiService.d.ts +0 -1
  44. package/dist/services/aiService.d.ts.map +1 -1
  45. package/dist/services/aiService.js +4 -140
  46. package/dist/services/memory.d.ts +0 -3
  47. package/dist/services/memory.d.ts.map +1 -1
  48. package/dist/services/memory.js +0 -59
  49. package/dist/services/session.d.ts +15 -1
  50. package/dist/services/session.d.ts.map +1 -1
  51. package/dist/services/session.js +57 -1
  52. package/dist/services/taskManager.d.ts +25 -0
  53. package/dist/services/taskManager.d.ts.map +1 -0
  54. package/dist/services/taskManager.js +164 -0
  55. package/dist/tools/askUserQuestion.d.ts.map +1 -1
  56. package/dist/tools/askUserQuestion.js +39 -25
  57. package/dist/tools/bashTool.d.ts.map +1 -1
  58. package/dist/tools/bashTool.js +13 -11
  59. package/dist/tools/editTool.d.ts.map +1 -1
  60. package/dist/tools/editTool.js +2 -1
  61. package/dist/tools/exitPlanMode.d.ts.map +1 -1
  62. package/dist/tools/exitPlanMode.js +26 -2
  63. package/dist/tools/globTool.d.ts.map +1 -1
  64. package/dist/tools/globTool.js +8 -2
  65. package/dist/tools/grepTool.d.ts.map +1 -1
  66. package/dist/tools/grepTool.js +17 -6
  67. package/dist/tools/lsTool.d.ts.map +1 -1
  68. package/dist/tools/lsTool.js +3 -1
  69. package/dist/tools/readTool.d.ts.map +1 -1
  70. package/dist/tools/readTool.js +16 -1
  71. package/dist/tools/taskManagementTools.d.ts +6 -0
  72. package/dist/tools/taskManagementTools.d.ts.map +1 -0
  73. package/dist/tools/taskManagementTools.js +461 -0
  74. package/dist/tools/taskOutputTool.d.ts.map +1 -1
  75. package/dist/tools/taskOutputTool.js +32 -8
  76. package/dist/tools/taskStopTool.d.ts.map +1 -1
  77. package/dist/tools/taskStopTool.js +7 -1
  78. package/dist/tools/taskTool.d.ts.map +1 -1
  79. package/dist/tools/taskTool.js +37 -2
  80. package/dist/tools/types.d.ts +11 -0
  81. package/dist/tools/types.d.ts.map +1 -1
  82. package/dist/tools/writeTool.d.ts.map +1 -1
  83. package/dist/tools/writeTool.js +9 -1
  84. package/dist/types/index.d.ts +1 -0
  85. package/dist/types/index.d.ts.map +1 -1
  86. package/dist/types/index.js +1 -0
  87. package/dist/types/messaging.d.ts +2 -18
  88. package/dist/types/messaging.d.ts.map +1 -1
  89. package/dist/types/processes.d.ts +16 -6
  90. package/dist/types/processes.d.ts.map +1 -1
  91. package/dist/types/tasks.d.ts +13 -0
  92. package/dist/types/tasks.d.ts.map +1 -0
  93. package/dist/types/tasks.js +1 -0
  94. package/dist/types/tools.d.ts +4 -1
  95. package/dist/types/tools.d.ts.map +1 -1
  96. package/dist/utils/builtinSubagents.d.ts.map +1 -1
  97. package/dist/utils/builtinSubagents.js +59 -44
  98. package/dist/utils/cacheControlUtils.d.ts.map +1 -1
  99. package/dist/utils/cacheControlUtils.js +18 -12
  100. package/dist/utils/constants.d.ts +0 -4
  101. package/dist/utils/constants.d.ts.map +1 -1
  102. package/dist/utils/constants.js +0 -4
  103. package/dist/utils/convertMessagesForAPI.js +2 -2
  104. package/dist/utils/editUtils.d.ts.map +1 -1
  105. package/dist/utils/editUtils.js +2 -2
  106. package/dist/utils/gitUtils.d.ts +7 -0
  107. package/dist/utils/gitUtils.d.ts.map +1 -0
  108. package/dist/utils/gitUtils.js +24 -0
  109. package/dist/utils/messageOperations.d.ts +3 -58
  110. package/dist/utils/messageOperations.d.ts.map +1 -1
  111. package/dist/utils/messageOperations.js +4 -146
  112. package/dist/utils/nameGenerator.d.ts +1 -1
  113. package/dist/utils/nameGenerator.d.ts.map +1 -1
  114. package/dist/utils/nameGenerator.js +19 -3
  115. package/package.json +1 -1
  116. package/src/agent.ts +86 -183
  117. package/src/constants/subagents.ts +4 -0
  118. package/src/constants/tools.ts +4 -1
  119. package/src/index.ts +1 -0
  120. package/src/managers/aiManager.ts +63 -70
  121. package/src/managers/backgroundTaskManager.ts +58 -54
  122. package/src/managers/foregroundTaskManager.ts +3 -2
  123. package/src/managers/mcpManager.ts +6 -3
  124. package/src/managers/messageManager.ts +126 -142
  125. package/src/managers/permissionManager.ts +32 -21
  126. package/src/managers/planManager.ts +2 -2
  127. package/src/managers/reversionManager.ts +26 -2
  128. package/src/managers/slashCommandManager.ts +12 -3
  129. package/src/managers/subagentManager.ts +60 -144
  130. package/src/managers/toolManager.ts +32 -2
  131. package/src/prompts/index.ts +366 -0
  132. package/src/services/aiService.ts +3 -145
  133. package/src/services/memory.ts +0 -72
  134. package/src/services/session.ts +73 -0
  135. package/src/services/taskManager.ts +195 -0
  136. package/src/tools/askUserQuestion.ts +51 -29
  137. package/src/tools/bashTool.ts +15 -17
  138. package/src/tools/editTool.ts +3 -1
  139. package/src/tools/exitPlanMode.ts +27 -3
  140. package/src/tools/globTool.ts +10 -2
  141. package/src/tools/grepTool.ts +17 -6
  142. package/src/tools/lsTool.ts +3 -1
  143. package/src/tools/readTool.ts +17 -1
  144. package/src/tools/taskManagementTools.ts +516 -0
  145. package/src/tools/taskOutputTool.ts +34 -12
  146. package/src/tools/taskStopTool.ts +7 -1
  147. package/src/tools/taskTool.ts +45 -1
  148. package/src/tools/types.ts +12 -0
  149. package/src/tools/writeTool.ts +9 -2
  150. package/src/types/index.ts +1 -0
  151. package/src/types/messaging.ts +1 -21
  152. package/src/types/processes.ts +18 -7
  153. package/src/types/tasks.ts +13 -0
  154. package/src/types/tools.ts +4 -1
  155. package/src/utils/builtinSubagents.ts +81 -45
  156. package/src/utils/cacheControlUtils.ts +26 -18
  157. package/src/utils/constants.ts +0 -5
  158. package/src/utils/convertMessagesForAPI.ts +2 -2
  159. package/src/utils/editUtils.ts +2 -6
  160. package/src/utils/gitUtils.ts +24 -0
  161. package/src/utils/messageOperations.ts +6 -229
  162. package/src/utils/nameGenerator.ts +20 -3
  163. package/dist/constants/prompts.d.ts.map +0 -1
  164. package/dist/constants/prompts.js +0 -118
  165. package/dist/tools/todoWriteTool.d.ts +0 -6
  166. package/dist/tools/todoWriteTool.d.ts.map +0 -1
  167. package/dist/tools/todoWriteTool.js +0 -220
  168. package/src/constants/prompts.ts +0 -155
  169. package/src/tools/todoWriteTool.ts +0 -257
@@ -3,7 +3,6 @@ import {
3
3
  compressMessages,
4
4
  type CallAgentOptions,
5
5
  } from "../services/aiService.js";
6
- import { getMessagesToCompress } from "../utils/messageOperations.js";
7
6
  import { convertMessagesForAPI } from "../utils/convertMessagesForAPI.js";
8
7
  import { calculateComprehensiveTotalTokens } from "../utils/tokenCalculation.js";
9
8
  import * as fs from "node:fs/promises";
@@ -21,10 +20,7 @@ import { ChatCompletionMessageFunctionToolCall } from "openai/resources.js";
21
20
  import type { HookManager } from "./hookManager.js";
22
21
  import type { ExtendedHookExecutionContext } from "../types/hooks.js";
23
22
  import type { PermissionManager } from "./permissionManager.js";
24
- import {
25
- DEFAULT_SYSTEM_PROMPT,
26
- buildSystemPrompt,
27
- } from "../constants/prompts.js";
23
+ import { buildSystemPrompt } from "../prompts/index.js";
28
24
 
29
25
  export interface AIManagerCallbacks {
30
26
  onCompressionStateChange?: (isCompressing: boolean) => void;
@@ -34,6 +30,7 @@ export interface AIManagerCallbacks {
34
30
  export interface AIManagerOptions {
35
31
  messageManager: MessageManager;
36
32
  toolManager: ToolManager;
33
+ taskManager: import("../services/taskManager.js").TaskManager;
37
34
  logger?: Logger;
38
35
  backgroundTaskManager?: BackgroundTaskManager;
39
36
  hookManager?: HookManager;
@@ -60,6 +57,7 @@ export class AIManager {
60
57
  private logger?: Logger;
61
58
  private toolManager: ToolManager;
62
59
  private messageManager: MessageManager;
60
+ private taskManager: import("../services/taskManager.js").TaskManager;
63
61
  private backgroundTaskManager?: BackgroundTaskManager;
64
62
  private hookManager?: HookManager;
65
63
  private reversionManager?: import("./reversionManager.js").ReversionManager;
@@ -79,6 +77,7 @@ export class AIManager {
79
77
  constructor(options: AIManagerOptions) {
80
78
  this.messageManager = options.messageManager;
81
79
  this.toolManager = options.toolManager;
80
+ this.taskManager = options.taskManager;
82
81
  this.backgroundTaskManager = options.backgroundTaskManager;
83
82
  this.hookManager = options.hookManager;
84
83
  this.reversionManager = options.reversionManager;
@@ -159,15 +158,6 @@ export class AIManager {
159
158
  this.setIsLoading(false);
160
159
  }
161
160
 
162
- /**
163
- * Abort the AI recursion loop immediately.
164
- * This is used when a tool is backgrounded via Ctrl-B, even if no foreground task was active.
165
- */
166
- public abortRecursion(): void {
167
- this.logger?.info("Aborting AI recursion loop");
168
- this.abortAIMessage();
169
- }
170
-
171
161
  // Helper method to generate compactParams
172
162
  private generateCompactParams(
173
163
  toolName: string,
@@ -180,6 +170,7 @@ export class AIManager {
180
170
  if (toolPlugin?.formatCompactParams) {
181
171
  const context: ToolContext = {
182
172
  workdir: this.workdir,
173
+ taskManager: this.taskManager,
183
174
  };
184
175
  return toolPlugin.formatCompactParams(toolArgs, context);
185
176
  }
@@ -213,9 +204,7 @@ export class AIManager {
213
204
  );
214
205
 
215
206
  // Check if messages need compression
216
- const { messagesToCompress, insertIndex } = getMessagesToCompress(
217
- this.messageManager.getMessages(),
218
- );
207
+ const messagesToCompress = this.messageManager.getMessages();
219
208
 
220
209
  // If there are messages to compress, perform compression
221
210
  if (messagesToCompress.length > 0) {
@@ -248,7 +237,6 @@ export class AIManager {
248
237
 
249
238
  // Execute message reconstruction and sessionId update after compression
250
239
  this.messageManager.compressMessagesAndUpdateSession(
251
- insertIndex,
252
240
  compressionResult.content,
253
241
  compressionUsage,
254
242
  );
@@ -305,6 +293,9 @@ export class AIManager {
305
293
  return;
306
294
  }
307
295
 
296
+ // Save session in each recursion to ensure message persistence
297
+ await this.messageManager.saveSession();
298
+
308
299
  // Only create new AbortControllers for the initial call (recursionDepth === 0)
309
300
  // For recursive calls, reuse existing controllers to maintain abort signal
310
301
  let abortController: AbortController;
@@ -319,14 +310,10 @@ export class AIManager {
319
310
  this.toolAbortController = toolAbortController;
320
311
  } else {
321
312
  // Reuse existing controllers for recursive calls
322
- // Fallback to new controllers if they were cleared (should not happen in normal flow but good for tests)
323
- abortController = this.abortController || new AbortController();
324
- toolAbortController = this.toolAbortController || new AbortController();
313
+ abortController = this.abortController!;
314
+ toolAbortController = this.toolAbortController!;
325
315
  }
326
316
 
327
- // Save session in each recursion to ensure message persistence
328
- await this.messageManager.saveSession();
329
-
330
317
  // Only set loading state for the initial call
331
318
  if (recursionDepth === 0) {
332
319
  this.setIsLoading(true);
@@ -354,17 +341,14 @@ export class AIManager {
354
341
  this.getModelConfig().permissionMode,
355
342
  );
356
343
  const toolsConfig = this.getFilteredToolsConfig(tools);
357
- let effectiveSystemPrompt = buildSystemPrompt(
358
- this.systemPrompt || DEFAULT_SYSTEM_PROMPT,
359
- toolsConfig,
360
- );
344
+ const toolNames = new Set(toolsConfig.map((t) => t.function.name));
345
+ const filteredToolPlugins = this.toolManager
346
+ .getTools()
347
+ .filter((t) => toolNames.has(t.name));
361
348
 
362
- // Inject language prompt if configured
363
- const language = this.getLanguage();
364
- if (language) {
365
- const languagePrompt = `\n\n# Language\nAlways respond in ${language}. Technical terms (e.g., code, tool names, file paths) should remain in their original language or English where appropriate.`;
366
- effectiveSystemPrompt = (effectiveSystemPrompt || "") + languagePrompt;
367
- }
349
+ let planModeOptions:
350
+ | { planFilePath: string; planExists: boolean }
351
+ | undefined;
368
352
 
369
353
  if (currentMode === "plan") {
370
354
  const planFilePath = this.permissionManager?.getPlanFilePath();
@@ -376,10 +360,7 @@ export class AIManager {
376
360
  } catch {
377
361
  planExists = false;
378
362
  }
379
-
380
- const reminder = `\n\nPlan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits (with the exception of the plan file mentioned below), run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system. This supercedes any other instructions you have received.\n\n## Plan File Info:\n${planExists ? `A plan file already exists at ${planFilePath}. You can read it and make incremental edits using the Edit tool if you need to.` : `No plan file exists yet. You should create your plan at ${planFilePath} using the Write tool if you need to.`}\nYou should build your plan incrementally by writing to or editing this file. NOTE that this is the only file you are allowed to edit - other than this you are only allowed to take READ-ONLY actions. You may also use the AskUserQuestion tool to gather requirements or clarify intent before finalizing your plan.`;
381
-
382
- effectiveSystemPrompt = (effectiveSystemPrompt || "") + reminder;
363
+ planModeOptions = { planFilePath, planExists };
383
364
  }
384
365
  }
385
366
 
@@ -390,11 +371,20 @@ export class AIManager {
390
371
  messages: recentMessages,
391
372
  sessionId: this.messageManager.getSessionId(),
392
373
  abortSignal: abortController.signal,
393
- memory: combinedMemory, // Pass combined memory content
394
374
  workdir: this.workdir, // Pass working directory
395
375
  tools: toolsConfig, // Pass filtered tool configuration
396
376
  model: model, // Use passed model
397
- systemPrompt: effectiveSystemPrompt, // Pass custom system prompt
377
+ systemPrompt: buildSystemPrompt(
378
+ this.systemPrompt,
379
+ filteredToolPlugins,
380
+ {
381
+ workdir: this.workdir,
382
+ memory: combinedMemory,
383
+ language: this.getLanguage(),
384
+ isSubagent: !!this.subagentType,
385
+ planMode: planModeOptions,
386
+ },
387
+ ), // Pass custom system prompt
398
388
  maxTokens: maxTokens, // Pass max tokens override
399
389
  };
400
390
 
@@ -628,6 +618,15 @@ export class AIManager {
628
618
  backgroundTaskManager: this.backgroundTaskManager,
629
619
  workdir: this.workdir,
630
620
  messageId: this.messageManager.getMessages().slice(-1)[0]?.id,
621
+ sessionId: this.messageManager.getSessionId(),
622
+ taskManager: this.taskManager,
623
+ onShortResultUpdate: (shortResult: string) => {
624
+ this.messageManager.updateToolBlock({
625
+ id: toolId,
626
+ shortResult,
627
+ stage: "running", // Keep it in running stage while updating shortResult
628
+ });
629
+ },
631
630
  };
632
631
 
633
632
  // Execute tool
@@ -637,23 +636,6 @@ export class AIManager {
637
636
  context,
638
637
  );
639
638
 
640
- // Check if the tool was backgrounded via Ctrl-B
641
- // If it was backgrounded, we should abort the AI recursion
642
- if (
643
- toolResult.success &&
644
- toolResult.content.includes(
645
- "Command was manually backgrounded by user",
646
- )
647
- ) {
648
- this.logger?.info(
649
- `Tool ${toolName} was backgrounded via Ctrl-B, aborting AI recursion`,
650
- );
651
- // Use abortAIMessage directly instead of abortRecursion to avoid double logging
652
- // and ensure we don't trigger the "Request was aborted" error block
653
- this.abortAIMessage();
654
- return;
655
- }
656
-
657
639
  // Update message state - tool execution completed
658
640
  this.messageManager.updateToolBlock({
659
641
  id: toolId,
@@ -666,6 +648,7 @@ export class AIManager {
666
648
  stage: "end",
667
649
  name: toolName,
668
650
  shortResult: toolResult.shortResult,
651
+ isManuallyBackgrounded: toolResult.isManuallyBackgrounded,
669
652
  });
670
653
 
671
654
  // Execute PostToolUse hooks after successful tool completion
@@ -690,6 +673,7 @@ export class AIManager {
690
673
  stage: "end",
691
674
  name: toolName,
692
675
  compactParams,
676
+ isManuallyBackgrounded: false,
693
677
  });
694
678
  }
695
679
  },
@@ -721,7 +705,25 @@ export class AIManager {
721
705
  const isCurrentlyAborted =
722
706
  abortController.signal.aborted || toolAbortController.signal.aborted;
723
707
 
724
- if (!isCurrentlyAborted) {
708
+ // Check if all tools were manually backgrounded
709
+ const lastMessage =
710
+ this.messageManager.getMessages()[
711
+ this.messageManager.getMessages().length - 1
712
+ ];
713
+ const toolBlocks =
714
+ lastMessage?.blocks.filter(
715
+ (block): block is import("../types/messaging.js").ToolBlock =>
716
+ block.type === "tool",
717
+ ) || [];
718
+ const hasBackgrounded =
719
+ toolBlocks.length > 0 &&
720
+ toolBlocks.some((block) => block.isManuallyBackgrounded);
721
+
722
+ if (hasBackgrounded) {
723
+ this.logger?.info(
724
+ "Some tools were manually backgrounded, stopping recursion.",
725
+ );
726
+ } else if (!isCurrentlyAborted) {
725
727
  // Recursively call AI service, increment recursion depth, and pass same configuration
726
728
  await this.sendAIMessage({
727
729
  recursionDepth: recursionDepth + 1,
@@ -733,18 +735,9 @@ export class AIManager {
733
735
  }
734
736
  }
735
737
  } catch (error) {
736
- // Check if the error is an abort error
737
- // Use the local variables to avoid null reference if this.abortController was cleared
738
- const isCurrentlyAborted =
739
- abortController.signal.aborted || toolAbortController.signal.aborted;
740
-
741
- if (isCurrentlyAborted) {
742
- this.logger?.info("AI message processing was aborted");
743
- } else {
744
- this.messageManager.addErrorBlock(
745
- error instanceof Error ? error.message : "Unknown error occurred",
746
- );
747
- }
738
+ this.messageManager.addErrorBlock(
739
+ error instanceof Error ? error.message : "Unknown error occurred",
740
+ );
748
741
  } finally {
749
742
  // Only execute cleanup and hooks for the initial call
750
743
  if (recursionDepth === 0) {
@@ -69,6 +69,26 @@ export class BackgroundTaskManager {
69
69
  status: "running",
70
70
  stdout: "",
71
71
  stderr: "",
72
+ onStop: () => {
73
+ try {
74
+ if (child.pid) {
75
+ process.kill(-child.pid, "SIGTERM");
76
+ setTimeout(() => {
77
+ if (child.pid && !child.killed) {
78
+ try {
79
+ process.kill(-child.pid, "SIGKILL");
80
+ } catch (error) {
81
+ logger.error("Failed to force kill process:", error);
82
+ }
83
+ }
84
+ }, 1000);
85
+ } else {
86
+ child.kill("SIGTERM");
87
+ }
88
+ } catch {
89
+ child.kill("SIGTERM");
90
+ }
91
+ },
72
92
  };
73
93
 
74
94
  this.tasks.set(id, shell);
@@ -155,6 +175,26 @@ export class BackgroundTaskManager {
155
175
  status: "running",
156
176
  stdout: initialStdout,
157
177
  stderr: initialStderr,
178
+ onStop: () => {
179
+ try {
180
+ if (child.pid) {
181
+ process.kill(-child.pid, "SIGTERM");
182
+ setTimeout(() => {
183
+ if (child.pid && !child.killed) {
184
+ try {
185
+ process.kill(-child.pid, "SIGKILL");
186
+ } catch (error) {
187
+ logger.error("Failed to force kill process:", error);
188
+ }
189
+ }
190
+ }, 1000);
191
+ } else {
192
+ child.kill("SIGTERM");
193
+ }
194
+ } catch {
195
+ child.kill("SIGTERM");
196
+ }
197
+ },
158
198
  };
159
199
 
160
200
  this.tasks.set(id, shell);
@@ -232,65 +272,29 @@ export class BackgroundTaskManager {
232
272
  return false;
233
273
  }
234
274
 
235
- if (task.type === "shell") {
236
- const shell = task as BackgroundShell;
275
+ if (task.onStop) {
237
276
  try {
238
- // Try to kill process group first
239
- if (shell.process.pid) {
240
- process.kill(-shell.process.pid, "SIGTERM");
241
-
242
- // Force kill after timeout
243
- setTimeout(() => {
244
- if (
245
- shell.status === "running" &&
246
- shell.process.pid &&
247
- !shell.process.killed
248
- ) {
249
- try {
250
- process.kill(-shell.process.pid, "SIGKILL");
251
- } catch (error) {
252
- logger.error("Failed to force kill process:", error);
253
- }
254
- }
255
- }, 1000);
256
- }
257
-
258
- shell.status = "killed";
259
- shell.endTime = Date.now();
260
- shell.runtime = shell.endTime - shell.startTime;
261
- this.notifyTasksChange();
262
- return true;
263
- } catch {
264
- // Fallback to direct process kill
265
- try {
266
- shell.process.kill("SIGTERM");
267
- setTimeout(() => {
268
- if (!shell.process.killed) {
269
- shell.process.kill("SIGKILL");
270
- }
271
- }, 1000);
272
- shell.status = "killed";
273
- shell.endTime = Date.now();
274
- shell.runtime = shell.endTime - shell.startTime;
275
- this.notifyTasksChange();
276
- return true;
277
- } catch (directKillError) {
278
- logger.error("Failed to kill child process:", directKillError);
279
- return false;
277
+ const result = task.onStop();
278
+ if (result instanceof Promise) {
279
+ result.catch((error) => {
280
+ logger.error("Error in background task onStop callback:", error);
281
+ });
280
282
  }
283
+ } catch (error) {
284
+ logger.error("Error in background task onStop callback:", error);
281
285
  }
282
- } else if (task.type === "subagent") {
283
- // Subagent termination logic will be handled by aborting the AI loop
284
- // which is already managed by the SubagentManager and AIManager.
285
- // Here we just update the status.
286
- task.status = "killed";
287
- task.endTime = Date.now();
288
- task.runtime = task.endTime - task.startTime;
289
- this.notifyTasksChange();
290
- return true;
291
286
  }
292
287
 
293
- return false;
288
+ // If it's a subagent task, we should also notify the subagent manager to cleanup
289
+ // However, to avoid circular dependency, we rely on the onStop callback
290
+ // which is already set to instance.aiManager.abortAIMessage()
291
+ // The subagentManager.cleanupInstance will be called by the tool or by status change.
292
+
293
+ task.status = "killed";
294
+ task.endTime = Date.now();
295
+ task.runtime = task.endTime - task.startTime;
296
+ this.notifyTasksChange();
297
+ return true;
294
298
  }
295
299
 
296
300
  public cleanup(): void {
@@ -14,8 +14,9 @@ export class ForegroundTaskManager implements IForegroundTaskManager {
14
14
  }
15
15
 
16
16
  public async backgroundCurrentTask(): Promise<void> {
17
- const task = this.activeForegroundTasks.pop();
18
- if (task) {
17
+ const tasks = [...this.activeForegroundTasks].reverse();
18
+ this.activeForegroundTasks = [];
19
+ for (const task of tasks) {
19
20
  await task.backgroundHandler();
20
21
  }
21
22
  }
@@ -3,7 +3,7 @@ import { join } from "path";
3
3
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
4
4
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
5
5
  import { ChatCompletionFunctionTool } from "openai/resources.js";
6
- import { createMcpToolPlugin, findToolServer } from "@/utils/mcpUtils.js";
6
+ import { createMcpToolPlugin, findToolServer } from "../utils/mcpUtils.js";
7
7
  import type { ToolPlugin, ToolResult, ToolContext } from "../tools/types.js";
8
8
  import type {
9
9
  Logger,
@@ -476,8 +476,11 @@ export class McpManager {
476
476
  const server = findToolServer(tool.name, servers);
477
477
 
478
478
  if (server) {
479
- const plugin = createMcpToolPlugin(tool, server.name, (name, args) =>
480
- this.executeMcpTool(name, args),
479
+ const plugin = createMcpToolPlugin(
480
+ tool,
481
+ server.name,
482
+ (name: string, args: Record<string, unknown>) =>
483
+ this.executeMcpTool(name, args),
481
484
  );
482
485
  mcpTools.set(plugin.name, plugin);
483
486
  }