wave-agent-sdk 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.d.ts +7 -2
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +58 -74
- package/dist/constants/prompts.d.ts +18 -14
- package/dist/constants/prompts.d.ts.map +1 -1
- package/dist/constants/prompts.js +134 -54
- package/dist/constants/tools.d.ts +4 -1
- package/dist/constants/tools.d.ts.map +1 -1
- package/dist/constants/tools.js +4 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/managers/aiManager.d.ts +2 -5
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +59 -48
- package/dist/managers/backgroundTaskManager.d.ts.map +1 -1
- package/dist/managers/backgroundTaskManager.js +59 -53
- package/dist/managers/foregroundTaskManager.d.ts.map +1 -1
- package/dist/managers/foregroundTaskManager.js +3 -2
- package/dist/managers/mcpManager.d.ts.map +1 -1
- package/dist/managers/messageManager.d.ts +7 -3
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +28 -24
- package/dist/managers/permissionManager.d.ts.map +1 -1
- package/dist/managers/permissionManager.js +25 -15
- package/dist/managers/planManager.d.ts +1 -1
- package/dist/managers/planManager.d.ts.map +1 -1
- package/dist/managers/planManager.js +2 -2
- package/dist/managers/subagentManager.d.ts +4 -0
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +22 -14
- package/dist/managers/toolManager.d.ts +11 -0
- package/dist/managers/toolManager.d.ts.map +1 -1
- package/dist/managers/toolManager.js +20 -2
- package/dist/services/aiService.d.ts +0 -1
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +4 -140
- package/dist/services/memory.d.ts +0 -3
- package/dist/services/memory.d.ts.map +1 -1
- package/dist/services/memory.js +0 -59
- package/dist/services/session.d.ts +3 -1
- package/dist/services/session.d.ts.map +1 -1
- package/dist/services/session.js +16 -1
- package/dist/services/taskManager.d.ts +21 -0
- package/dist/services/taskManager.d.ts.map +1 -0
- package/dist/services/taskManager.js +158 -0
- package/dist/tools/askUserQuestion.d.ts.map +1 -1
- package/dist/tools/askUserQuestion.js +39 -25
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +7 -9
- package/dist/tools/editTool.d.ts.map +1 -1
- package/dist/tools/editTool.js +2 -1
- package/dist/tools/exitPlanMode.d.ts.map +1 -1
- package/dist/tools/exitPlanMode.js +25 -1
- package/dist/tools/globTool.d.ts.map +1 -1
- package/dist/tools/globTool.js +8 -2
- package/dist/tools/grepTool.d.ts.map +1 -1
- package/dist/tools/grepTool.js +17 -6
- package/dist/tools/lsTool.d.ts.map +1 -1
- package/dist/tools/lsTool.js +3 -1
- package/dist/tools/readTool.d.ts.map +1 -1
- package/dist/tools/readTool.js +16 -1
- package/dist/tools/taskManagementTools.d.ts +6 -0
- package/dist/tools/taskManagementTools.d.ts.map +1 -0
- package/dist/tools/taskManagementTools.js +453 -0
- package/dist/tools/taskOutputTool.d.ts.map +1 -1
- package/dist/tools/taskOutputTool.js +32 -8
- package/dist/tools/taskStopTool.d.ts.map +1 -1
- package/dist/tools/taskStopTool.js +7 -1
- package/dist/tools/taskTool.d.ts.map +1 -1
- package/dist/tools/taskTool.js +6 -1
- package/dist/tools/types.d.ts +9 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/writeTool.d.ts.map +1 -1
- package/dist/tools/writeTool.js +9 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/messaging.d.ts +2 -8
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/types/processes.d.ts +11 -6
- package/dist/types/processes.d.ts.map +1 -1
- package/dist/types/tasks.d.ts +13 -0
- package/dist/types/tasks.d.ts.map +1 -0
- package/dist/types/tasks.js +1 -0
- package/dist/types/tools.d.ts +4 -1
- package/dist/types/tools.d.ts.map +1 -1
- package/dist/utils/builtinSubagents.d.ts.map +1 -1
- package/dist/utils/builtinSubagents.js +38 -1
- package/dist/utils/cacheControlUtils.d.ts.map +1 -1
- package/dist/utils/cacheControlUtils.js +18 -12
- package/dist/utils/constants.d.ts +0 -4
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/constants.js +0 -4
- package/dist/utils/convertMessagesForAPI.js +2 -2
- package/dist/utils/messageOperations.d.ts +2 -30
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +4 -79
- package/dist/utils/nameGenerator.d.ts +1 -1
- package/dist/utils/nameGenerator.d.ts.map +1 -1
- package/dist/utils/nameGenerator.js +19 -3
- package/package.json +1 -1
- package/src/agent.ts +79 -84
- package/src/constants/prompts.ts +161 -65
- package/src/constants/tools.ts +4 -1
- package/src/index.ts +1 -0
- package/src/managers/aiManager.ts +79 -70
- package/src/managers/backgroundTaskManager.ts +53 -54
- package/src/managers/foregroundTaskManager.ts +3 -2
- package/src/managers/mcpManager.ts +6 -3
- package/src/managers/messageManager.ts +37 -26
- package/src/managers/permissionManager.ts +32 -21
- package/src/managers/planManager.ts +2 -2
- package/src/managers/subagentManager.ts +33 -14
- package/src/managers/toolManager.ts +32 -2
- package/src/services/aiService.ts +3 -145
- package/src/services/memory.ts +0 -72
- package/src/services/session.ts +21 -0
- package/src/services/taskManager.ts +188 -0
- package/src/tools/askUserQuestion.ts +51 -29
- package/src/tools/bashTool.ts +9 -15
- package/src/tools/editTool.ts +3 -1
- package/src/tools/exitPlanMode.ts +26 -2
- package/src/tools/globTool.ts +10 -2
- package/src/tools/grepTool.ts +17 -6
- package/src/tools/lsTool.ts +3 -1
- package/src/tools/readTool.ts +17 -1
- package/src/tools/taskManagementTools.ts +498 -0
- package/src/tools/taskOutputTool.ts +34 -12
- package/src/tools/taskStopTool.ts +7 -1
- package/src/tools/taskTool.ts +7 -1
- package/src/tools/types.ts +10 -0
- package/src/tools/writeTool.ts +9 -2
- package/src/types/index.ts +1 -0
- package/src/types/messaging.ts +1 -9
- package/src/types/processes.ts +13 -7
- package/src/types/tasks.ts +13 -0
- package/src/types/tools.ts +4 -1
- package/src/utils/builtinSubagents.ts +47 -1
- package/src/utils/cacheControlUtils.ts +26 -18
- package/src/utils/constants.ts +0 -5
- package/src/utils/convertMessagesForAPI.ts +2 -2
- package/src/utils/messageOperations.ts +5 -116
- package/src/utils/nameGenerator.ts +20 -3
- package/dist/tools/todoWriteTool.d.ts +0 -6
- package/dist/tools/todoWriteTool.d.ts.map +0 -1
- package/dist/tools/todoWriteTool.js +0 -220
- package/src/tools/todoWriteTool.ts +0 -257
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import * as os from "node:os";
|
|
2
|
+
import * as path from "node:path";
|
|
1
3
|
import {
|
|
2
4
|
callAgent,
|
|
3
5
|
compressMessages,
|
|
4
6
|
type CallAgentOptions,
|
|
5
7
|
} from "../services/aiService.js";
|
|
6
|
-
import { getMessagesToCompress } from "../utils/messageOperations.js";
|
|
7
8
|
import { convertMessagesForAPI } from "../utils/convertMessagesForAPI.js";
|
|
8
9
|
import { calculateComprehensiveTotalTokens } from "../utils/tokenCalculation.js";
|
|
10
|
+
import * as fsSync from "node:fs";
|
|
9
11
|
import * as fs from "node:fs/promises";
|
|
10
12
|
import type {
|
|
11
13
|
Logger,
|
|
@@ -21,10 +23,24 @@ import { ChatCompletionMessageFunctionToolCall } from "openai/resources.js";
|
|
|
21
23
|
import type { HookManager } from "./hookManager.js";
|
|
22
24
|
import type { ExtendedHookExecutionContext } from "../types/hooks.js";
|
|
23
25
|
import type { PermissionManager } from "./permissionManager.js";
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
import { buildSystemPrompt } from "../constants/prompts.js";
|
|
27
|
+
|
|
28
|
+
function isGitRepository(dirPath: string): string {
|
|
29
|
+
try {
|
|
30
|
+
// Check if .git directory exists in current directory or any parent directory
|
|
31
|
+
let currentPath = path.resolve(dirPath);
|
|
32
|
+
while (currentPath !== path.dirname(currentPath)) {
|
|
33
|
+
const gitPath = path.join(currentPath, ".git");
|
|
34
|
+
if (fsSync.existsSync(gitPath)) {
|
|
35
|
+
return "Yes";
|
|
36
|
+
}
|
|
37
|
+
currentPath = path.dirname(currentPath);
|
|
38
|
+
}
|
|
39
|
+
return "No";
|
|
40
|
+
} catch {
|
|
41
|
+
return "No";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
28
44
|
|
|
29
45
|
export interface AIManagerCallbacks {
|
|
30
46
|
onCompressionStateChange?: (isCompressing: boolean) => void;
|
|
@@ -34,6 +50,7 @@ export interface AIManagerCallbacks {
|
|
|
34
50
|
export interface AIManagerOptions {
|
|
35
51
|
messageManager: MessageManager;
|
|
36
52
|
toolManager: ToolManager;
|
|
53
|
+
taskManager: import("../services/taskManager.js").TaskManager;
|
|
37
54
|
logger?: Logger;
|
|
38
55
|
backgroundTaskManager?: BackgroundTaskManager;
|
|
39
56
|
hookManager?: HookManager;
|
|
@@ -60,6 +77,7 @@ export class AIManager {
|
|
|
60
77
|
private logger?: Logger;
|
|
61
78
|
private toolManager: ToolManager;
|
|
62
79
|
private messageManager: MessageManager;
|
|
80
|
+
private taskManager: import("../services/taskManager.js").TaskManager;
|
|
63
81
|
private backgroundTaskManager?: BackgroundTaskManager;
|
|
64
82
|
private hookManager?: HookManager;
|
|
65
83
|
private reversionManager?: import("./reversionManager.js").ReversionManager;
|
|
@@ -79,6 +97,7 @@ export class AIManager {
|
|
|
79
97
|
constructor(options: AIManagerOptions) {
|
|
80
98
|
this.messageManager = options.messageManager;
|
|
81
99
|
this.toolManager = options.toolManager;
|
|
100
|
+
this.taskManager = options.taskManager;
|
|
82
101
|
this.backgroundTaskManager = options.backgroundTaskManager;
|
|
83
102
|
this.hookManager = options.hookManager;
|
|
84
103
|
this.reversionManager = options.reversionManager;
|
|
@@ -159,15 +178,6 @@ export class AIManager {
|
|
|
159
178
|
this.setIsLoading(false);
|
|
160
179
|
}
|
|
161
180
|
|
|
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
181
|
// Helper method to generate compactParams
|
|
172
182
|
private generateCompactParams(
|
|
173
183
|
toolName: string,
|
|
@@ -180,6 +190,7 @@ export class AIManager {
|
|
|
180
190
|
if (toolPlugin?.formatCompactParams) {
|
|
181
191
|
const context: ToolContext = {
|
|
182
192
|
workdir: this.workdir,
|
|
193
|
+
taskManager: this.taskManager,
|
|
183
194
|
};
|
|
184
195
|
return toolPlugin.formatCompactParams(toolArgs, context);
|
|
185
196
|
}
|
|
@@ -213,9 +224,7 @@ export class AIManager {
|
|
|
213
224
|
);
|
|
214
225
|
|
|
215
226
|
// Check if messages need compression
|
|
216
|
-
const
|
|
217
|
-
this.messageManager.getMessages(),
|
|
218
|
-
);
|
|
227
|
+
const messagesToCompress = this.messageManager.getMessages();
|
|
219
228
|
|
|
220
229
|
// If there are messages to compress, perform compression
|
|
221
230
|
if (messagesToCompress.length > 0) {
|
|
@@ -248,7 +257,6 @@ export class AIManager {
|
|
|
248
257
|
|
|
249
258
|
// Execute message reconstruction and sessionId update after compression
|
|
250
259
|
this.messageManager.compressMessagesAndUpdateSession(
|
|
251
|
-
insertIndex,
|
|
252
260
|
compressionResult.content,
|
|
253
261
|
compressionUsage,
|
|
254
262
|
);
|
|
@@ -305,6 +313,9 @@ export class AIManager {
|
|
|
305
313
|
return;
|
|
306
314
|
}
|
|
307
315
|
|
|
316
|
+
// Save session in each recursion to ensure message persistence
|
|
317
|
+
await this.messageManager.saveSession();
|
|
318
|
+
|
|
308
319
|
// Only create new AbortControllers for the initial call (recursionDepth === 0)
|
|
309
320
|
// For recursive calls, reuse existing controllers to maintain abort signal
|
|
310
321
|
let abortController: AbortController;
|
|
@@ -319,14 +330,10 @@ export class AIManager {
|
|
|
319
330
|
this.toolAbortController = toolAbortController;
|
|
320
331
|
} else {
|
|
321
332
|
// Reuse existing controllers for recursive calls
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
toolAbortController = this.toolAbortController || new AbortController();
|
|
333
|
+
abortController = this.abortController!;
|
|
334
|
+
toolAbortController = this.toolAbortController!;
|
|
325
335
|
}
|
|
326
336
|
|
|
327
|
-
// Save session in each recursion to ensure message persistence
|
|
328
|
-
await this.messageManager.saveSession();
|
|
329
|
-
|
|
330
337
|
// Only set loading state for the initial call
|
|
331
338
|
if (recursionDepth === 0) {
|
|
332
339
|
this.setIsLoading(true);
|
|
@@ -354,17 +361,14 @@ export class AIManager {
|
|
|
354
361
|
this.getModelConfig().permissionMode,
|
|
355
362
|
);
|
|
356
363
|
const toolsConfig = this.getFilteredToolsConfig(tools);
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
364
|
+
const toolNames = new Set(toolsConfig.map((t) => t.function.name));
|
|
365
|
+
const filteredToolPlugins = this.toolManager
|
|
366
|
+
.getTools()
|
|
367
|
+
.filter((t) => toolNames.has(t.name));
|
|
361
368
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
-
}
|
|
369
|
+
let planModeOptions:
|
|
370
|
+
| { planFilePath: string; planExists: boolean }
|
|
371
|
+
| undefined;
|
|
368
372
|
|
|
369
373
|
if (currentMode === "plan") {
|
|
370
374
|
const planFilePath = this.permissionManager?.getPlanFilePath();
|
|
@@ -376,10 +380,7 @@ export class AIManager {
|
|
|
376
380
|
} catch {
|
|
377
381
|
planExists = false;
|
|
378
382
|
}
|
|
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;
|
|
383
|
+
planModeOptions = { planFilePath, planExists };
|
|
383
384
|
}
|
|
384
385
|
}
|
|
385
386
|
|
|
@@ -390,11 +391,23 @@ export class AIManager {
|
|
|
390
391
|
messages: recentMessages,
|
|
391
392
|
sessionId: this.messageManager.getSessionId(),
|
|
392
393
|
abortSignal: abortController.signal,
|
|
393
|
-
memory: combinedMemory, // Pass combined memory content
|
|
394
394
|
workdir: this.workdir, // Pass working directory
|
|
395
395
|
tools: toolsConfig, // Pass filtered tool configuration
|
|
396
396
|
model: model, // Use passed model
|
|
397
|
-
systemPrompt:
|
|
397
|
+
systemPrompt: buildSystemPrompt(
|
|
398
|
+
this.systemPrompt,
|
|
399
|
+
filteredToolPlugins,
|
|
400
|
+
{
|
|
401
|
+
workdir: this.workdir,
|
|
402
|
+
isGitRepo: isGitRepository(this.workdir),
|
|
403
|
+
platform: os.platform(),
|
|
404
|
+
osVersion: `${os.type()} ${os.release()}`,
|
|
405
|
+
today: new Date().toISOString().split("T")[0],
|
|
406
|
+
memory: combinedMemory,
|
|
407
|
+
language: this.getLanguage(),
|
|
408
|
+
planMode: planModeOptions,
|
|
409
|
+
},
|
|
410
|
+
), // Pass custom system prompt
|
|
398
411
|
maxTokens: maxTokens, // Pass max tokens override
|
|
399
412
|
};
|
|
400
413
|
|
|
@@ -628,6 +641,8 @@ export class AIManager {
|
|
|
628
641
|
backgroundTaskManager: this.backgroundTaskManager,
|
|
629
642
|
workdir: this.workdir,
|
|
630
643
|
messageId: this.messageManager.getMessages().slice(-1)[0]?.id,
|
|
644
|
+
sessionId: this.messageManager.getSessionId(),
|
|
645
|
+
taskManager: this.taskManager,
|
|
631
646
|
};
|
|
632
647
|
|
|
633
648
|
// Execute tool
|
|
@@ -637,23 +652,6 @@ export class AIManager {
|
|
|
637
652
|
context,
|
|
638
653
|
);
|
|
639
654
|
|
|
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
655
|
// Update message state - tool execution completed
|
|
658
656
|
this.messageManager.updateToolBlock({
|
|
659
657
|
id: toolId,
|
|
@@ -666,6 +664,7 @@ export class AIManager {
|
|
|
666
664
|
stage: "end",
|
|
667
665
|
name: toolName,
|
|
668
666
|
shortResult: toolResult.shortResult,
|
|
667
|
+
isManuallyBackgrounded: toolResult.isManuallyBackgrounded,
|
|
669
668
|
});
|
|
670
669
|
|
|
671
670
|
// Execute PostToolUse hooks after successful tool completion
|
|
@@ -690,6 +689,7 @@ export class AIManager {
|
|
|
690
689
|
stage: "end",
|
|
691
690
|
name: toolName,
|
|
692
691
|
compactParams,
|
|
692
|
+
isManuallyBackgrounded: false,
|
|
693
693
|
});
|
|
694
694
|
}
|
|
695
695
|
},
|
|
@@ -721,7 +721,25 @@ export class AIManager {
|
|
|
721
721
|
const isCurrentlyAborted =
|
|
722
722
|
abortController.signal.aborted || toolAbortController.signal.aborted;
|
|
723
723
|
|
|
724
|
-
if
|
|
724
|
+
// Check if all tools were manually backgrounded
|
|
725
|
+
const lastMessage =
|
|
726
|
+
this.messageManager.getMessages()[
|
|
727
|
+
this.messageManager.getMessages().length - 1
|
|
728
|
+
];
|
|
729
|
+
const toolBlocks =
|
|
730
|
+
lastMessage?.blocks.filter(
|
|
731
|
+
(block): block is import("../types/messaging.js").ToolBlock =>
|
|
732
|
+
block.type === "tool",
|
|
733
|
+
) || [];
|
|
734
|
+
const hasBackgrounded =
|
|
735
|
+
toolBlocks.length > 0 &&
|
|
736
|
+
toolBlocks.some((block) => block.isManuallyBackgrounded);
|
|
737
|
+
|
|
738
|
+
if (hasBackgrounded) {
|
|
739
|
+
this.logger?.info(
|
|
740
|
+
"Some tools were manually backgrounded, stopping recursion.",
|
|
741
|
+
);
|
|
742
|
+
} else if (!isCurrentlyAborted) {
|
|
725
743
|
// Recursively call AI service, increment recursion depth, and pass same configuration
|
|
726
744
|
await this.sendAIMessage({
|
|
727
745
|
recursionDepth: recursionDepth + 1,
|
|
@@ -733,18 +751,9 @@ export class AIManager {
|
|
|
733
751
|
}
|
|
734
752
|
}
|
|
735
753
|
} catch (error) {
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
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
|
-
}
|
|
754
|
+
this.messageManager.addErrorBlock(
|
|
755
|
+
error instanceof Error ? error.message : "Unknown error occurred",
|
|
756
|
+
);
|
|
748
757
|
} finally {
|
|
749
758
|
// Only execute cleanup and hooks for the initial call
|
|
750
759
|
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,24 @@ export class BackgroundTaskManager {
|
|
|
232
272
|
return false;
|
|
233
273
|
}
|
|
234
274
|
|
|
235
|
-
if (task.
|
|
236
|
-
const shell = task as BackgroundShell;
|
|
275
|
+
if (task.onStop) {
|
|
237
276
|
try {
|
|
238
|
-
|
|
239
|
-
if (
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
288
|
+
task.status = "killed";
|
|
289
|
+
task.endTime = Date.now();
|
|
290
|
+
task.runtime = task.endTime - task.startTime;
|
|
291
|
+
this.notifyTasksChange();
|
|
292
|
+
return true;
|
|
294
293
|
}
|
|
295
294
|
|
|
296
295
|
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
|
|
18
|
-
|
|
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 "
|
|
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(
|
|
480
|
-
|
|
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
|
}
|
|
@@ -45,7 +45,7 @@ export interface MessageManagerCallbacks {
|
|
|
45
45
|
onAssistantReasoningUpdated?: (chunk: string, accumulated: string) => void;
|
|
46
46
|
onToolBlockUpdated?: (params: AgentToolBlockUpdateParams) => void;
|
|
47
47
|
onErrorBlockAdded?: (error: string) => void;
|
|
48
|
-
onCompressBlockAdded?: (
|
|
48
|
+
onCompressBlockAdded?: (content: string) => void;
|
|
49
49
|
onCompressionStateChange?: (isCompressing: boolean) => void;
|
|
50
50
|
onMemoryBlockAdded?: (
|
|
51
51
|
content: string,
|
|
@@ -74,6 +74,10 @@ export interface MessageManagerCallbacks {
|
|
|
74
74
|
subagentId: string,
|
|
75
75
|
status: "active" | "completed" | "error" | "aborted",
|
|
76
76
|
) => void;
|
|
77
|
+
onFileHistoryBlockAdded?: (
|
|
78
|
+
snapshots: import("../types/reversion.js").FileSnapshot[],
|
|
79
|
+
) => void;
|
|
80
|
+
onSubagentTaskStopRequested?: (subagentId: string) => void;
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
export interface MessageManagerOptions {
|
|
@@ -88,6 +92,7 @@ export interface MessageManagerOptions {
|
|
|
88
92
|
export class MessageManager {
|
|
89
93
|
// Private state properties
|
|
90
94
|
private sessionId: string;
|
|
95
|
+
private rootSessionId: string;
|
|
91
96
|
private messages: Message[];
|
|
92
97
|
private latestTotalTokens: number;
|
|
93
98
|
private userInputHistory: string[];
|
|
@@ -104,6 +109,7 @@ export class MessageManager {
|
|
|
104
109
|
|
|
105
110
|
constructor(options: MessageManagerOptions) {
|
|
106
111
|
this.sessionId = generateSessionId();
|
|
112
|
+
this.rootSessionId = this.sessionId;
|
|
107
113
|
this.messages = [];
|
|
108
114
|
this.latestTotalTokens = 0;
|
|
109
115
|
this.userInputHistory = [];
|
|
@@ -125,6 +131,10 @@ export class MessageManager {
|
|
|
125
131
|
return this.sessionId;
|
|
126
132
|
}
|
|
127
133
|
|
|
134
|
+
public getRootSessionId(): string {
|
|
135
|
+
return this.rootSessionId;
|
|
136
|
+
}
|
|
137
|
+
|
|
128
138
|
public getMessages(): Message[] {
|
|
129
139
|
return [...this.messages];
|
|
130
140
|
}
|
|
@@ -247,6 +257,7 @@ export class MessageManager {
|
|
|
247
257
|
unsavedMessages, // Only append new messages
|
|
248
258
|
this.workdir,
|
|
249
259
|
this.sessionType,
|
|
260
|
+
this.rootSessionId,
|
|
250
261
|
);
|
|
251
262
|
|
|
252
263
|
// Update the saved message count
|
|
@@ -297,6 +308,7 @@ export class MessageManager {
|
|
|
297
308
|
// Initialize state from session data
|
|
298
309
|
public initializeFromSession(sessionData: SessionData): void {
|
|
299
310
|
this.setSessionId(sessionData.id);
|
|
311
|
+
this.rootSessionId = sessionData.rootSessionId || sessionData.id;
|
|
300
312
|
this.setMessages([...sessionData.messages]);
|
|
301
313
|
this.updateFilesInContext(sessionData.messages);
|
|
302
314
|
this.setlatestTotalTokens(sessionData.metadata.latestTotalTokens);
|
|
@@ -402,17 +414,7 @@ export class MessageManager {
|
|
|
402
414
|
public updateToolBlock(params: AgentToolBlockUpdateParams): void {
|
|
403
415
|
const newMessages = updateToolBlockInMessage({
|
|
404
416
|
messages: this.messages,
|
|
405
|
-
|
|
406
|
-
parameters: params.parameters,
|
|
407
|
-
result: params.result,
|
|
408
|
-
success: params.success,
|
|
409
|
-
error: params.error,
|
|
410
|
-
stage: params.stage,
|
|
411
|
-
name: params.name,
|
|
412
|
-
shortResult: params.shortResult,
|
|
413
|
-
images: params.images,
|
|
414
|
-
compactParams: params.compactParams,
|
|
415
|
-
parametersChunk: params.parametersChunk,
|
|
417
|
+
...params,
|
|
416
418
|
});
|
|
417
419
|
this.setMessages(newMessages);
|
|
418
420
|
this.callbacks.onToolBlockUpdated?.(params);
|
|
@@ -442,14 +444,14 @@ export class MessageManager {
|
|
|
442
444
|
}
|
|
443
445
|
|
|
444
446
|
/**
|
|
445
|
-
* Compress messages and update session, delete compressed messages, only keep compressed messages and
|
|
447
|
+
* Compress messages and update session, delete compressed messages, only keep compressed messages and last 3 messages
|
|
446
448
|
*/
|
|
447
449
|
public compressMessagesAndUpdateSession(
|
|
448
|
-
insertIndex: number,
|
|
449
450
|
compressedContent: string,
|
|
450
451
|
usage?: Usage,
|
|
451
452
|
): void {
|
|
452
|
-
|
|
453
|
+
// Get last 3 messages to preserve
|
|
454
|
+
const lastThreeMessages = this.messages.slice(-3);
|
|
453
455
|
|
|
454
456
|
// Create compressed message
|
|
455
457
|
const compressMessage: Message = {
|
|
@@ -464,24 +466,22 @@ export class MessageManager {
|
|
|
464
466
|
...(usage && { usage }),
|
|
465
467
|
};
|
|
466
468
|
|
|
467
|
-
//
|
|
468
|
-
const
|
|
469
|
-
insertIndex < 0 ? currentMessages.length + insertIndex : insertIndex;
|
|
470
|
-
|
|
471
|
-
// Build new message array: keep compressed message and all messages from actualIndex onwards
|
|
472
|
-
const newMessages: Message[] = [
|
|
473
|
-
compressMessage,
|
|
474
|
-
...currentMessages.slice(actualIndex),
|
|
475
|
-
];
|
|
469
|
+
// Build new message array: keep the compressed message and last 3 messages
|
|
470
|
+
const newMessages: Message[] = [compressMessage, ...lastThreeMessages];
|
|
476
471
|
|
|
477
472
|
// Update sessionId
|
|
478
473
|
this.setSessionId(generateSessionId());
|
|
479
474
|
|
|
475
|
+
// Trigger task list update if this is the main session to ensure continuity
|
|
476
|
+
if (this.sessionType === "main") {
|
|
477
|
+
this.callbacks.onSessionIdChange?.(this.sessionId);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
480
|
// Set new message list
|
|
481
481
|
this.setMessages(newMessages);
|
|
482
482
|
|
|
483
|
-
// Trigger compression callback
|
|
484
|
-
this.callbacks.onCompressBlockAdded?.(
|
|
483
|
+
// Trigger compression callback
|
|
484
|
+
this.callbacks.onCompressBlockAdded?.(compressedContent);
|
|
485
485
|
}
|
|
486
486
|
|
|
487
487
|
public addFileHistoryBlock(
|
|
@@ -496,6 +496,7 @@ export class MessageManager {
|
|
|
496
496
|
snapshots,
|
|
497
497
|
} as unknown as import("../types/index.js").MessageBlock);
|
|
498
498
|
this.setMessages([...this.messages]);
|
|
499
|
+
this.callbacks.onFileHistoryBlockAdded?.(snapshots);
|
|
499
500
|
}
|
|
500
501
|
}
|
|
501
502
|
|
|
@@ -729,6 +730,16 @@ export class MessageManager {
|
|
|
729
730
|
|
|
730
731
|
// Truncate messages in memory
|
|
731
732
|
const newMessages = this.messages.slice(0, index);
|
|
733
|
+
|
|
734
|
+
// Identify subagent tasks to stop
|
|
735
|
+
for (const message of messagesToRemove) {
|
|
736
|
+
for (const block of message.blocks) {
|
|
737
|
+
if (block.type === "subagent" && block.subagentId) {
|
|
738
|
+
this.callbacks.onSubagentTaskStopRequested?.(block.subagentId);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
732
743
|
this.setMessages(newMessages);
|
|
733
744
|
|
|
734
745
|
// Update persistence: rewrite the session file
|