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.
- package/dist/agent.d.ts +28 -5
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +54 -37
- package/dist/constants/tools.d.ts +2 -2
- package/dist/constants/tools.js +2 -2
- package/dist/managers/MemoryRuleManager.js +1 -1
- package/dist/managers/aiManager.d.ts +3 -3
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +3 -4
- package/dist/managers/backgroundBashManager.d.ts.map +1 -1
- package/dist/managers/backgroundBashManager.js +1 -0
- package/dist/managers/backgroundTaskManager.d.ts +35 -0
- package/dist/managers/backgroundTaskManager.d.ts.map +1 -0
- package/dist/managers/backgroundTaskManager.js +249 -0
- package/dist/managers/foregroundTaskManager.d.ts +9 -0
- package/dist/managers/foregroundTaskManager.d.ts.map +1 -0
- package/dist/managers/foregroundTaskManager.js +20 -0
- package/dist/managers/liveConfigManager.d.ts +1 -1
- package/dist/managers/lspManager.d.ts.map +1 -1
- package/dist/managers/lspManager.js +3 -1
- package/dist/managers/messageManager.d.ts +12 -2
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +36 -2
- package/dist/managers/permissionManager.d.ts.map +1 -1
- package/dist/managers/permissionManager.js +1 -7
- package/dist/managers/pluginManager.d.ts.map +1 -1
- package/dist/managers/pluginManager.js +3 -2
- package/dist/managers/slashCommandManager.d.ts +3 -0
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +1 -0
- package/dist/managers/subagentManager.d.ts +11 -2
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +137 -39
- package/dist/managers/toolManager.d.ts +7 -1
- package/dist/managers/toolManager.d.ts.map +1 -1
- package/dist/managers/toolManager.js +9 -3
- package/dist/services/GitService.d.ts.map +1 -1
- package/dist/services/GitService.js +6 -2
- package/dist/services/MarketplaceService.d.ts +2 -2
- package/dist/services/MarketplaceService.d.ts.map +1 -1
- package/dist/services/MarketplaceService.js +18 -11
- package/dist/services/MemoryRuleService.d.ts +1 -1
- package/dist/services/MemoryRuleService.d.ts.map +1 -1
- package/dist/services/MemoryRuleService.js +13 -2
- package/dist/services/memory.js +1 -1
- package/dist/tools/bashTool.d.ts +0 -8
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +44 -172
- package/dist/tools/editTool.d.ts.map +1 -1
- package/dist/tools/editTool.js +6 -5
- package/dist/tools/multiEditTool.d.ts.map +1 -1
- package/dist/tools/multiEditTool.js +7 -6
- package/dist/tools/taskOutputTool.d.ts +3 -0
- package/dist/tools/taskOutputTool.d.ts.map +1 -0
- package/dist/tools/taskOutputTool.js +149 -0
- package/dist/tools/taskStopTool.d.ts +3 -0
- package/dist/tools/taskStopTool.d.ts.map +1 -0
- package/dist/tools/taskStopTool.js +65 -0
- package/dist/tools/taskTool.d.ts.map +1 -1
- package/dist/tools/taskTool.js +105 -63
- package/dist/tools/types.d.ts +3 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/types/marketplace.d.ts +1 -0
- package/dist/types/marketplace.d.ts.map +1 -1
- package/dist/types/messaging.d.ts +1 -0
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/types/processes.d.ts +24 -4
- package/dist/types/processes.d.ts.map +1 -1
- package/dist/utils/editUtils.d.ts +2 -11
- package/dist/utils/editUtils.d.ts.map +1 -1
- package/dist/utils/editUtils.js +52 -79
- package/dist/utils/messageOperations.d.ts +3 -1
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +5 -1
- package/package.json +5 -5
- package/src/agent.ts +73 -45
- package/src/constants/tools.ts +2 -2
- package/src/managers/MemoryRuleManager.ts +1 -1
- package/src/managers/aiManager.ts +6 -9
- package/src/managers/backgroundBashManager.ts +1 -0
- package/src/managers/backgroundTaskManager.ts +306 -0
- package/src/managers/foregroundTaskManager.ts +26 -0
- package/src/managers/lspManager.ts +3 -1
- package/src/managers/messageManager.ts +48 -2
- package/src/managers/permissionManager.ts +1 -7
- package/src/managers/pluginManager.ts +4 -3
- package/src/managers/slashCommandManager.ts +4 -0
- package/src/managers/subagentManager.ts +167 -35
- package/src/managers/toolManager.ts +16 -4
- package/src/services/GitService.ts +6 -2
- package/src/services/MarketplaceService.ts +30 -12
- package/src/services/MemoryRuleService.ts +18 -6
- package/src/services/memory.ts +1 -1
- package/src/tools/bashTool.ts +59 -196
- package/src/tools/editTool.ts +6 -17
- package/src/tools/multiEditTool.ts +7 -18
- package/src/tools/taskOutputTool.ts +174 -0
- package/src/tools/taskStopTool.ts +72 -0
- package/src/tools/taskTool.ts +130 -74
- package/src/tools/types.ts +3 -0
- package/src/types/marketplace.ts +1 -0
- package/src/types/messaging.ts +1 -0
- package/src/types/processes.ts +33 -4
- package/src/utils/editUtils.ts +65 -103
- 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
|
-
|
|
17
|
-
type
|
|
18
|
-
} from "./managers/
|
|
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
|
-
|
|
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
|
|
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.
|
|
204
|
-
|
|
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
|
-
|
|
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
|
|
460
|
-
|
|
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.
|
|
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.
|
|
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
|
|
948
|
-
this.
|
|
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
|
package/src/constants/tools.ts
CHANGED
|
@@ -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
|
|
4
|
-
export const
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
618
|
+
backgroundTaskManager: this.backgroundTaskManager,
|
|
622
619
|
workdir: this.workdir,
|
|
623
620
|
messageId: this.messageManager.getMessages().slice(-1)[0]?.id,
|
|
624
621
|
};
|
|
@@ -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
|
-
|
|
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}`,
|