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
package/src/agent.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { ForegroundTaskManager } from "./managers/foregroundTaskManager.js";
1
2
  import {
2
3
  MessageManager,
3
4
  type MessageManagerCallbacks,
@@ -13,9 +14,9 @@ import { McpManager, type McpManagerCallbacks } from "./managers/mcpManager.js";
13
14
  import { LspManager } from "./managers/lspManager.js";
14
15
  import { BashManager } from "./managers/bashManager.js";
15
16
  import {
16
- BackgroundBashManager,
17
- type BackgroundBashManagerCallbacks,
18
- } from "./managers/backgroundBashManager.js";
17
+ BackgroundTaskManager,
18
+ type BackgroundTaskManagerCallbacks,
19
+ } from "./managers/backgroundTaskManager.js";
19
20
  import { SlashCommandManager } from "./managers/slashCommandManager.js";
20
21
  import { PluginManager } from "./managers/pluginManager.js";
21
22
  import { HookManager } from "./managers/hookManager.js";
@@ -38,6 +39,8 @@ import type {
38
39
  Usage,
39
40
  PermissionMode,
40
41
  PermissionCallback,
42
+ BackgroundTask,
43
+ ForegroundTask,
41
44
  } from "./types/index.js";
42
45
  import { MemoryRuleManager } from "./managers/MemoryRuleManager.js";
43
46
  import { LiveConfigManager } from "./managers/liveConfigManager.js";
@@ -100,10 +103,12 @@ export interface AgentOptions {
100
103
 
101
104
  export interface AgentCallbacks
102
105
  extends MessageManagerCallbacks,
103
- BackgroundBashManagerCallbacks,
106
+ BackgroundTaskManagerCallbacks,
104
107
  McpManagerCallbacks,
105
108
  SubagentManagerCallbacks {
109
+ onTasksChange?: (tasks: BackgroundTask[]) => void;
106
110
  onPermissionModeChange?: (mode: PermissionMode) => void;
111
+ onBackgroundCurrentTask?: () => void;
107
112
  }
108
113
 
109
114
  export class Agent {
@@ -111,7 +116,7 @@ export class Agent {
111
116
  private aiManager: AIManager;
112
117
 
113
118
  private bashManager: BashManager | null = null;
114
- private backgroundBashManager: BackgroundBashManager;
119
+ private backgroundTaskManager: BackgroundTaskManager;
115
120
  private logger?: Logger; // Add optional logger property
116
121
  private toolManager: ToolManager; // Add tool registry instance
117
122
  private mcpManager: McpManager; // Add MCP manager instance
@@ -126,6 +131,7 @@ export class Agent {
126
131
  private reversionManager: ReversionManager;
127
132
  private memoryRuleManager: MemoryRuleManager; // Add memory rule manager instance
128
133
  private liveConfigManager: LiveConfigManager; // Add live configuration manager
134
+ private foregroundTaskManager: ForegroundTaskManager;
129
135
  private configurationService: ConfigurationService; // Add configuration service
130
136
  private workdir: string; // Working directory
131
137
  private systemPrompt?: string; // Custom system prompt
@@ -200,8 +206,15 @@ export class Agent {
200
206
  // Store options for dynamic configuration resolution
201
207
  this.options = options;
202
208
 
203
- this.backgroundBashManager = new BackgroundBashManager({
204
- callbacks,
209
+ this.foregroundTaskManager = new ForegroundTaskManager();
210
+
211
+ this.backgroundTaskManager = new BackgroundTaskManager({
212
+ callbacks: {
213
+ ...callbacks,
214
+ onTasksChange: (tasks) => {
215
+ callbacks.onTasksChange?.(tasks);
216
+ },
217
+ },
205
218
  workdir: this.workdir,
206
219
  });
207
220
  this.mcpManager = new McpManager({ callbacks, logger: this.logger }); // Initialize MCP manager
@@ -230,11 +243,17 @@ export class Agent {
230
243
  workdir: this.workdir,
231
244
  });
232
245
 
246
+ // Initialize memory rule manager
247
+ this.memoryRuleManager = new MemoryRuleManager({
248
+ workdir: this.workdir,
249
+ });
250
+
233
251
  // Initialize MessageManager
234
252
  this.messageManager = new MessageManager({
235
253
  callbacks,
236
254
  workdir: this.workdir,
237
255
  logger: this.logger,
256
+ memoryRuleManager: this.memoryRuleManager,
238
257
  });
239
258
 
240
259
  // Initialize ReversionManager
@@ -242,11 +261,6 @@ export class Agent {
242
261
  new ReversionService(this.messageManager.getTranscriptPath()),
243
262
  );
244
263
 
245
- // Initialize memory rule manager
246
- this.memoryRuleManager = new MemoryRuleManager({
247
- workdir: this.workdir,
248
- });
249
-
250
264
  // Create a wrapper for canUseTool that triggers notification hooks
251
265
  const canUseToolWithNotification: PermissionCallback | undefined =
252
266
  options.canUseTool
@@ -298,6 +312,8 @@ export class Agent {
298
312
  reversionManager: this.reversionManager,
299
313
  permissionMode: options.permissionMode, // Let PermissionManager handle defaultMode resolution
300
314
  canUseToolCallback: canUseToolWithNotification,
315
+ backgroundTaskManager: this.backgroundTaskManager,
316
+ foregroundTaskManager: this.foregroundTaskManager,
301
317
  }); // Initialize tool registry with permission support
302
318
  this.liveConfigManager = new LiveConfigManager({
303
319
  workdir: this.workdir,
@@ -331,6 +347,8 @@ export class Agent {
331
347
  getLanguage: () => this.getLanguage(),
332
348
  hookManager: this.hookManager,
333
349
  onUsageAdded: (usage) => this.addUsage(usage),
350
+ backgroundTaskManager: this.backgroundTaskManager,
351
+ memoryRuleManager: this.memoryRuleManager,
334
352
  });
335
353
 
336
354
  // Initialize AI manager with resolved configuration
@@ -338,7 +356,7 @@ export class Agent {
338
356
  messageManager: this.messageManager,
339
357
  toolManager: this.toolManager,
340
358
  logger: this.logger,
341
- backgroundBashManager: this.backgroundBashManager,
359
+ backgroundTaskManager: this.backgroundTaskManager,
342
360
  hookManager: this.hookManager,
343
361
  permissionManager: this.permissionManager,
344
362
  callbacks: {
@@ -362,6 +380,7 @@ export class Agent {
362
380
  this.slashCommandManager = new SlashCommandManager({
363
381
  messageManager: this.messageManager,
364
382
  aiManager: this.aiManager,
383
+ backgroundTaskManager: this.backgroundTaskManager,
365
384
  workdir: this.workdir,
366
385
  logger: this.logger,
367
386
  });
@@ -456,34 +475,8 @@ export class Agent {
456
475
  }
457
476
 
458
477
  /** Get combined memory content (project + user + modular rules) */
459
- public get combinedMemory(): string {
460
- let combined = "";
461
- if (this._projectMemoryContent.trim()) {
462
- combined += this._projectMemoryContent;
463
- }
464
- if (this._userMemoryContent.trim()) {
465
- if (combined) {
466
- combined += "\n\n";
467
- }
468
- combined += this._userMemoryContent;
469
- }
470
-
471
- // Add modular memory rules
472
- const filesInContext = this.messageManager.getFilesInContext();
473
- const activeRules = this.memoryRuleManager.getActiveRules(filesInContext);
474
- if (activeRules.length > 0) {
475
- this.logger?.debug(
476
- `Active modular rules (${activeRules.length}): ${activeRules.map((r) => r.id).join(", ")}`,
477
- );
478
- if (combined) {
479
- combined += "\n\n";
480
- }
481
- combined += activeRules.map((r) => r.content).join("\n\n");
482
- } else {
483
- this.logger?.debug("No active modular rules for current context");
484
- }
485
-
486
- return combined;
478
+ public async getCombinedMemory(): Promise<string> {
479
+ return this.messageManager.getCombinedMemory();
487
480
  }
488
481
 
489
482
  /** Get AI loading status */
@@ -506,12 +499,25 @@ export class Agent {
506
499
  id: string,
507
500
  filter?: string,
508
501
  ): { stdout: string; stderr: string; status: string } | null {
509
- return this.backgroundBashManager.getOutput(id, filter);
502
+ return this.backgroundTaskManager.getOutput(id, filter);
510
503
  }
511
504
 
512
505
  /** Kill background bash shell */
513
506
  public killBackgroundShell(id: string): boolean {
514
- return this.backgroundBashManager.killShell(id);
507
+ return this.backgroundTaskManager.stopTask(id);
508
+ }
509
+
510
+ /** Get background task output */
511
+ public getBackgroundTaskOutput(
512
+ id: string,
513
+ filter?: string,
514
+ ): { stdout: string; stderr: string; status: string } | null {
515
+ return this.backgroundTaskManager.getOutput(id, filter);
516
+ }
517
+
518
+ /** Stop background task */
519
+ public stopBackgroundTask(id: string): boolean {
520
+ return this.backgroundTaskManager.stopTask(id);
515
521
  }
516
522
 
517
523
  /**
@@ -938,14 +944,36 @@ export class Agent {
938
944
  this.slashCommandManager.abortCurrentCommand();
939
945
  }
940
946
 
947
+ /**
948
+ * Register a foreground task that can be backgrounded
949
+ */
950
+ public registerForegroundTask(task: ForegroundTask): void {
951
+ this.foregroundTaskManager.registerForegroundTask(task);
952
+ }
953
+
954
+ /**
955
+ * Unregister a foreground task
956
+ */
957
+ public unregisterForegroundTask(id: string): void {
958
+ this.foregroundTaskManager.unregisterForegroundTask(id);
959
+ }
960
+
961
+ /**
962
+ * Background the current foreground task
963
+ */
964
+ public async backgroundCurrentTask(): Promise<void> {
965
+ await this.foregroundTaskManager.backgroundCurrentTask();
966
+ this.options.callbacks?.onBackgroundCurrentTask?.();
967
+ }
968
+
941
969
  /** Destroy managers, clean up resources */
942
970
  public async destroy(): Promise<void> {
943
971
  await this.messageManager.saveSession();
944
972
  this.abortAIMessage(); // This will abort tools including Task tool (subagents)
945
973
  this.abortBashCommand();
946
974
  this.abortSlashCommand();
947
- // Cleanup background bash manager
948
- this.backgroundBashManager.cleanup();
975
+ // Cleanup background task manager
976
+ this.backgroundTaskManager.cleanup();
949
977
  // Cleanup MCP connections
950
978
  await this.mcpManager.cleanup();
951
979
  // Cleanup LSP connections
@@ -1,7 +1,7 @@
1
1
  export const ASK_USER_QUESTION_TOOL_NAME = "AskUserQuestion";
2
2
  export const BASH_TOOL_NAME = "Bash";
3
- export const BASH_OUTPUT_TOOL_NAME = "BashOutput";
4
- export const KILL_BASH_TOOL_NAME = "KillBash";
3
+ export const TASK_OUTPUT_TOOL_NAME = "TaskOutput";
4
+ export const TASK_STOP_TOOL_NAME = "TaskStop";
5
5
  export const DELETE_FILE_TOOL_NAME = "Delete";
6
6
  export const EDIT_TOOL_NAME = "Edit";
7
7
  export const EXIT_PLAN_MODE_TOOL_NAME = "ExitPlanMode";
@@ -148,7 +148,7 @@ export class MemoryRuleManager {
148
148
  getActiveRules(filesInContext: string[]): MemoryRule[] {
149
149
  const activeRules: MemoryRule[] = [];
150
150
  for (const rule of Object.values(this.state.rules)) {
151
- if (this.service.isRuleActive(rule, filesInContext)) {
151
+ if (this.service.isRuleActive(rule, filesInContext, this.workdir)) {
152
152
  activeRules.push(rule);
153
153
  }
154
154
  }
@@ -6,7 +6,6 @@ import {
6
6
  import { getMessagesToCompress } from "../utils/messageOperations.js";
7
7
  import { convertMessagesForAPI } from "../utils/convertMessagesForAPI.js";
8
8
  import { calculateComprehensiveTotalTokens } from "../utils/tokenCalculation.js";
9
- import * as memory from "../services/memory.js";
10
9
  import * as fs from "node:fs/promises";
11
10
  import type {
12
11
  Logger,
@@ -17,7 +16,7 @@ import type {
17
16
  import type { ToolManager } from "./toolManager.js";
18
17
  import type { ToolContext, ToolResult } from "../tools/types.js";
19
18
  import type { MessageManager } from "./messageManager.js";
20
- import type { BackgroundBashManager } from "./backgroundBashManager.js";
19
+ import type { BackgroundTaskManager } from "./backgroundTaskManager.js";
21
20
  import { ChatCompletionMessageFunctionToolCall } from "openai/resources.js";
22
21
  import type { HookManager } from "./hookManager.js";
23
22
  import type { ExtendedHookExecutionContext } from "../types/hooks.js";
@@ -36,7 +35,7 @@ export interface AIManagerOptions {
36
35
  messageManager: MessageManager;
37
36
  toolManager: ToolManager;
38
37
  logger?: Logger;
39
- backgroundBashManager?: BackgroundBashManager;
38
+ backgroundTaskManager?: BackgroundTaskManager;
40
39
  hookManager?: HookManager;
41
40
  permissionManager?: PermissionManager;
42
41
  callbacks?: AIManagerCallbacks;
@@ -61,7 +60,7 @@ export class AIManager {
61
60
  private logger?: Logger;
62
61
  private toolManager: ToolManager;
63
62
  private messageManager: MessageManager;
64
- private backgroundBashManager?: BackgroundBashManager;
63
+ private backgroundTaskManager?: BackgroundTaskManager;
65
64
  private hookManager?: HookManager;
66
65
  private reversionManager?: import("./reversionManager.js").ReversionManager;
67
66
  private permissionManager?: PermissionManager;
@@ -80,7 +79,7 @@ export class AIManager {
80
79
  constructor(options: AIManagerOptions) {
81
80
  this.messageManager = options.messageManager;
82
81
  this.toolManager = options.toolManager;
83
- this.backgroundBashManager = options.backgroundBashManager;
82
+ this.backgroundTaskManager = options.backgroundTaskManager;
84
83
  this.hookManager = options.hookManager;
85
84
  this.reversionManager = options.reversionManager;
86
85
  this.permissionManager = options.permissionManager;
@@ -333,9 +332,7 @@ export class AIManager {
333
332
 
334
333
  try {
335
334
  // Get combined memory content
336
- const combinedMemory = await memory.getCombinedMemoryContent(
337
- this.workdir,
338
- );
335
+ const combinedMemory = await this.messageManager.getCombinedMemory();
339
336
 
340
337
  // Track if assistant message has been created
341
338
  let assistantMessageCreated = false;
@@ -618,7 +615,7 @@ export class AIManager {
618
615
  // Create tool execution context
619
616
  const context: ToolContext = {
620
617
  abortSignal: toolAbortController.signal,
621
- backgroundBashManager: this.backgroundBashManager,
618
+ backgroundTaskManager: this.backgroundTaskManager,
622
619
  workdir: this.workdir,
623
620
  messageId: this.messageManager.getMessages().slice(-1)[0]?.id,
624
621
  };
@@ -42,6 +42,7 @@ export class BackgroundBashManager {
42
42
 
43
43
  const shell: BackgroundShell = {
44
44
  id,
45
+ type: "shell",
45
46
  process: child,
46
47
  command,
47
48
  startTime,
@@ -0,0 +1,306 @@
1
+ import { spawn, type ChildProcess } from "child_process";
2
+ import { BackgroundTask, BackgroundShell } from "../types/processes.js";
3
+ import { stripAnsiColors } from "../utils/stringUtils.js";
4
+ import { logger } from "../utils/globalLogger.js";
5
+
6
+ export interface BackgroundTaskManagerCallbacks {
7
+ onTasksChange?: (tasks: BackgroundTask[]) => void;
8
+ }
9
+
10
+ export interface BackgroundTaskManagerOptions {
11
+ callbacks?: BackgroundTaskManagerCallbacks;
12
+ workdir: string;
13
+ }
14
+
15
+ export class BackgroundTaskManager {
16
+ private tasks = new Map<string, BackgroundTask>();
17
+ private nextId = 1;
18
+ private callbacks: BackgroundTaskManagerCallbacks;
19
+ private workdir: string;
20
+
21
+ constructor(options: BackgroundTaskManagerOptions) {
22
+ this.callbacks = options.callbacks || {};
23
+ this.workdir = options.workdir;
24
+ }
25
+
26
+ private notifyTasksChange(): void {
27
+ this.callbacks.onTasksChange?.(Array.from(this.tasks.values()));
28
+ }
29
+
30
+ public generateId(): string {
31
+ return `task_${this.nextId++}`;
32
+ }
33
+
34
+ public addTask(task: BackgroundTask): void {
35
+ this.tasks.set(task.id, task);
36
+ this.notifyTasksChange();
37
+ }
38
+
39
+ public getTask(id: string): BackgroundTask | undefined {
40
+ return this.tasks.get(id);
41
+ }
42
+
43
+ public getAllTasks(): BackgroundTask[] {
44
+ return Array.from(this.tasks.values());
45
+ }
46
+
47
+ public startShell(
48
+ command: string,
49
+ timeout?: number,
50
+ ): { id: string; child: ChildProcess; detach: () => void } {
51
+ const id = this.generateId();
52
+ const startTime = Date.now();
53
+
54
+ const child = spawn(command, {
55
+ shell: true,
56
+ stdio: "pipe",
57
+ cwd: this.workdir,
58
+ env: {
59
+ ...process.env,
60
+ },
61
+ });
62
+
63
+ const shell: BackgroundShell = {
64
+ id,
65
+ type: "shell",
66
+ process: child,
67
+ command,
68
+ startTime,
69
+ status: "running",
70
+ stdout: "",
71
+ stderr: "",
72
+ };
73
+
74
+ this.tasks.set(id, shell);
75
+ this.notifyTasksChange();
76
+
77
+ // Set up timeout if specified
78
+ let timeoutHandle: NodeJS.Timeout | undefined;
79
+ if (timeout && timeout > 0) {
80
+ timeoutHandle = setTimeout(() => {
81
+ if (shell.status === "running") {
82
+ this.stopTask(id);
83
+ }
84
+ }, timeout);
85
+ }
86
+
87
+ const onStdout = (data: Buffer | string) => {
88
+ shell.stdout += stripAnsiColors(data.toString());
89
+ this.notifyTasksChange();
90
+ };
91
+
92
+ const onStderr = (data: Buffer | string) => {
93
+ shell.stderr += stripAnsiColors(data.toString());
94
+ this.notifyTasksChange();
95
+ };
96
+
97
+ const onExit = (code: number | null) => {
98
+ if (timeoutHandle) {
99
+ clearTimeout(timeoutHandle);
100
+ }
101
+ shell.status = code === 0 ? "completed" : "failed";
102
+ shell.exitCode = code ?? 0;
103
+ shell.endTime = Date.now();
104
+ shell.runtime = shell.endTime - startTime;
105
+ this.notifyTasksChange();
106
+ };
107
+
108
+ const onError = (error: Error) => {
109
+ if (timeoutHandle) {
110
+ clearTimeout(timeoutHandle);
111
+ }
112
+ shell.status = "failed";
113
+ shell.stderr += `\nProcess error: ${stripAnsiColors(error.message)}`;
114
+ shell.exitCode = 1;
115
+ shell.endTime = Date.now();
116
+ shell.runtime = shell.endTime - startTime;
117
+ this.notifyTasksChange();
118
+ };
119
+
120
+ child.stdout?.on("data", onStdout);
121
+ child.stderr?.on("data", onStderr);
122
+ child.on("exit", onExit);
123
+ child.on("error", onError);
124
+
125
+ const detach = () => {
126
+ child.stdout?.off("data", onStdout);
127
+ child.stderr?.off("data", onStderr);
128
+ child.off("exit", onExit);
129
+ child.off("error", onError);
130
+ if (timeoutHandle) {
131
+ clearTimeout(timeoutHandle);
132
+ }
133
+ this.tasks.delete(id);
134
+ this.notifyTasksChange();
135
+ };
136
+
137
+ return { id, child, detach };
138
+ }
139
+
140
+ public adoptProcess(
141
+ child: ChildProcess,
142
+ command: string,
143
+ initialStdout: string = "",
144
+ initialStderr: string = "",
145
+ ): string {
146
+ const id = this.generateId();
147
+ const startTime = Date.now();
148
+
149
+ const shell: BackgroundShell = {
150
+ id,
151
+ type: "shell",
152
+ process: child,
153
+ command,
154
+ startTime,
155
+ status: "running",
156
+ stdout: initialStdout,
157
+ stderr: initialStderr,
158
+ };
159
+
160
+ this.tasks.set(id, shell);
161
+ this.notifyTasksChange();
162
+
163
+ child.stdout?.on("data", (data) => {
164
+ shell.stdout += stripAnsiColors(data.toString());
165
+ this.notifyTasksChange();
166
+ });
167
+
168
+ child.stderr?.on("data", (data) => {
169
+ shell.stderr += stripAnsiColors(data.toString());
170
+ this.notifyTasksChange();
171
+ });
172
+
173
+ child.on("exit", (code) => {
174
+ shell.status = code === 0 ? "completed" : "failed";
175
+ shell.exitCode = code ?? 0;
176
+ shell.endTime = Date.now();
177
+ shell.runtime = shell.endTime - startTime;
178
+ this.notifyTasksChange();
179
+ });
180
+
181
+ child.on("error", (error) => {
182
+ shell.status = "failed";
183
+ shell.stderr += `\nProcess error: ${stripAnsiColors(error.message)}`;
184
+ shell.exitCode = 1;
185
+ shell.endTime = Date.now();
186
+ shell.runtime = shell.endTime - startTime;
187
+ this.notifyTasksChange();
188
+ });
189
+
190
+ return id;
191
+ }
192
+
193
+ public getOutput(
194
+ id: string,
195
+ filter?: string,
196
+ ): { stdout: string; stderr: string; status: string } | null {
197
+ const task = this.tasks.get(id);
198
+ if (!task) {
199
+ return null;
200
+ }
201
+
202
+ let stdout = task.stdout;
203
+ let stderr = task.stderr;
204
+
205
+ // Apply regex filter if provided
206
+ if (filter) {
207
+ try {
208
+ const regex = new RegExp(filter);
209
+ stdout = stdout
210
+ .split("\n")
211
+ .filter((line) => regex.test(line))
212
+ .join("\n");
213
+ stderr = stderr
214
+ .split("\n")
215
+ .filter((line) => regex.test(line))
216
+ .join("\n");
217
+ } catch (error) {
218
+ logger.warn(`Invalid filter regex: ${filter}`, error);
219
+ }
220
+ }
221
+
222
+ return {
223
+ stdout,
224
+ stderr,
225
+ status: task.status,
226
+ };
227
+ }
228
+
229
+ public stopTask(id: string): boolean {
230
+ const task = this.tasks.get(id);
231
+ if (!task || task.status !== "running") {
232
+ return false;
233
+ }
234
+
235
+ if (task.type === "shell") {
236
+ const shell = task as BackgroundShell;
237
+ 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;
280
+ }
281
+ }
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
+ }
292
+
293
+ return false;
294
+ }
295
+
296
+ public cleanup(): void {
297
+ // Kill all running tasks
298
+ for (const [id, task] of this.tasks) {
299
+ if (task.status === "running") {
300
+ this.stopTask(id);
301
+ }
302
+ }
303
+ this.tasks.clear();
304
+ this.notifyTasksChange();
305
+ }
306
+ }
@@ -0,0 +1,26 @@
1
+ import { ForegroundTask, IForegroundTaskManager } from "../types/processes.js";
2
+
3
+ export class ForegroundTaskManager implements IForegroundTaskManager {
4
+ private activeForegroundTasks: ForegroundTask[] = [];
5
+
6
+ public registerForegroundTask(task: ForegroundTask): void {
7
+ this.activeForegroundTasks.push(task);
8
+ }
9
+
10
+ public unregisterForegroundTask(id: string): void {
11
+ this.activeForegroundTasks = this.activeForegroundTasks.filter(
12
+ (t) => t.id !== id,
13
+ );
14
+ }
15
+
16
+ public async backgroundCurrentTask(): Promise<void> {
17
+ const task = this.activeForegroundTasks.pop();
18
+ if (task) {
19
+ await task.backgroundHandler();
20
+ }
21
+ }
22
+
23
+ public hasActiveTasks(): boolean {
24
+ return this.activeForegroundTasks.length > 0;
25
+ }
26
+ }
@@ -418,7 +418,9 @@ export class LspManager implements ILspManager {
418
418
  await this.sendRequest(lspProc, "shutdown", {}, timeout);
419
419
  await this.sendNotification(lspProc, "exit", {});
420
420
  // Give it a moment to exit
421
- await new Promise((resolve) => setTimeout(resolve, 100));
421
+ if (timeout > 100) {
422
+ await new Promise((resolve) => setTimeout(resolve, 100));
423
+ }
422
424
  } catch (error) {
423
425
  this.logger?.debug(
424
426
  `Failed to gracefully shutdown LSP for ${language}: ${error}`,