wave-agent-sdk 0.5.1 → 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 -69
- 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 -0
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +54 -16
- 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 +18 -2
- 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 +5 -1
- 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 -78
- 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 +71 -26
- 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 +29 -2
- 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 +5 -1
- 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;
|
|
@@ -171,6 +190,7 @@ export class AIManager {
|
|
|
171
190
|
if (toolPlugin?.formatCompactParams) {
|
|
172
191
|
const context: ToolContext = {
|
|
173
192
|
workdir: this.workdir,
|
|
193
|
+
taskManager: this.taskManager,
|
|
174
194
|
};
|
|
175
195
|
return toolPlugin.formatCompactParams(toolArgs, context);
|
|
176
196
|
}
|
|
@@ -204,9 +224,7 @@ export class AIManager {
|
|
|
204
224
|
);
|
|
205
225
|
|
|
206
226
|
// Check if messages need compression
|
|
207
|
-
const
|
|
208
|
-
this.messageManager.getMessages(),
|
|
209
|
-
);
|
|
227
|
+
const messagesToCompress = this.messageManager.getMessages();
|
|
210
228
|
|
|
211
229
|
// If there are messages to compress, perform compression
|
|
212
230
|
if (messagesToCompress.length > 0) {
|
|
@@ -239,7 +257,6 @@ export class AIManager {
|
|
|
239
257
|
|
|
240
258
|
// Execute message reconstruction and sessionId update after compression
|
|
241
259
|
this.messageManager.compressMessagesAndUpdateSession(
|
|
242
|
-
insertIndex,
|
|
243
260
|
compressionResult.content,
|
|
244
261
|
compressionUsage,
|
|
245
262
|
);
|
|
@@ -344,17 +361,14 @@ export class AIManager {
|
|
|
344
361
|
this.getModelConfig().permissionMode,
|
|
345
362
|
);
|
|
346
363
|
const toolsConfig = this.getFilteredToolsConfig(tools);
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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));
|
|
351
368
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
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.`;
|
|
356
|
-
effectiveSystemPrompt = (effectiveSystemPrompt || "") + languagePrompt;
|
|
357
|
-
}
|
|
369
|
+
let planModeOptions:
|
|
370
|
+
| { planFilePath: string; planExists: boolean }
|
|
371
|
+
| undefined;
|
|
358
372
|
|
|
359
373
|
if (currentMode === "plan") {
|
|
360
374
|
const planFilePath = this.permissionManager?.getPlanFilePath();
|
|
@@ -366,10 +380,7 @@ export class AIManager {
|
|
|
366
380
|
} catch {
|
|
367
381
|
planExists = false;
|
|
368
382
|
}
|
|
369
|
-
|
|
370
|
-
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.`;
|
|
371
|
-
|
|
372
|
-
effectiveSystemPrompt = (effectiveSystemPrompt || "") + reminder;
|
|
383
|
+
planModeOptions = { planFilePath, planExists };
|
|
373
384
|
}
|
|
374
385
|
}
|
|
375
386
|
|
|
@@ -380,11 +391,23 @@ export class AIManager {
|
|
|
380
391
|
messages: recentMessages,
|
|
381
392
|
sessionId: this.messageManager.getSessionId(),
|
|
382
393
|
abortSignal: abortController.signal,
|
|
383
|
-
memory: combinedMemory, // Pass combined memory content
|
|
384
394
|
workdir: this.workdir, // Pass working directory
|
|
385
395
|
tools: toolsConfig, // Pass filtered tool configuration
|
|
386
396
|
model: model, // Use passed model
|
|
387
|
-
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
|
|
388
411
|
maxTokens: maxTokens, // Pass max tokens override
|
|
389
412
|
};
|
|
390
413
|
|
|
@@ -618,6 +641,8 @@ export class AIManager {
|
|
|
618
641
|
backgroundTaskManager: this.backgroundTaskManager,
|
|
619
642
|
workdir: this.workdir,
|
|
620
643
|
messageId: this.messageManager.getMessages().slice(-1)[0]?.id,
|
|
644
|
+
sessionId: this.messageManager.getSessionId(),
|
|
645
|
+
taskManager: this.taskManager,
|
|
621
646
|
};
|
|
622
647
|
|
|
623
648
|
// Execute tool
|
|
@@ -639,6 +664,7 @@ export class AIManager {
|
|
|
639
664
|
stage: "end",
|
|
640
665
|
name: toolName,
|
|
641
666
|
shortResult: toolResult.shortResult,
|
|
667
|
+
isManuallyBackgrounded: toolResult.isManuallyBackgrounded,
|
|
642
668
|
});
|
|
643
669
|
|
|
644
670
|
// Execute PostToolUse hooks after successful tool completion
|
|
@@ -663,6 +689,7 @@ export class AIManager {
|
|
|
663
689
|
stage: "end",
|
|
664
690
|
name: toolName,
|
|
665
691
|
compactParams,
|
|
692
|
+
isManuallyBackgrounded: false,
|
|
666
693
|
});
|
|
667
694
|
}
|
|
668
695
|
},
|
|
@@ -694,7 +721,25 @@ export class AIManager {
|
|
|
694
721
|
const isCurrentlyAborted =
|
|
695
722
|
abortController.signal.aborted || toolAbortController.signal.aborted;
|
|
696
723
|
|
|
697
|
-
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) {
|
|
698
743
|
// Recursively call AI service, increment recursion depth, and pass same configuration
|
|
699
744
|
await this.sendAIMessage({
|
|
700
745
|
recursionDepth: recursionDepth + 1,
|
|
@@ -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
|
|
@@ -323,40 +323,30 @@ export class PermissionManager {
|
|
|
323
323
|
workdir,
|
|
324
324
|
);
|
|
325
325
|
if (!isInside) {
|
|
326
|
-
this.logger?.
|
|
327
|
-
"File operation outside the Safe Zone in acceptEdits mode",
|
|
326
|
+
this.logger?.info(
|
|
327
|
+
"File operation outside the Safe Zone in acceptEdits mode, falling back to manual confirmation",
|
|
328
328
|
{
|
|
329
329
|
toolName: context.toolName,
|
|
330
330
|
targetPath,
|
|
331
331
|
resolvedPath,
|
|
332
332
|
},
|
|
333
333
|
);
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
334
|
+
// Fall through to normal permission check flow to trigger confirmation prompt
|
|
335
|
+
} else {
|
|
336
|
+
this.logger?.debug(
|
|
337
|
+
"Permission automatically accepted for tool in acceptEdits mode",
|
|
338
|
+
{
|
|
339
|
+
toolName: context.toolName,
|
|
340
|
+
},
|
|
341
|
+
);
|
|
342
|
+
return { behavior: "allow" };
|
|
338
343
|
}
|
|
339
344
|
}
|
|
340
|
-
|
|
341
|
-
this.logger?.debug(
|
|
342
|
-
"Permission automatically accepted for tool in acceptEdits mode",
|
|
343
|
-
{
|
|
344
|
-
toolName: context.toolName,
|
|
345
|
-
},
|
|
346
|
-
);
|
|
347
|
-
return { behavior: "allow" };
|
|
348
345
|
}
|
|
349
346
|
}
|
|
350
347
|
|
|
351
348
|
// 1.3 If plan mode, allow Read-only tools and Edit/Write for plan file
|
|
352
349
|
if (context.permissionMode === "plan") {
|
|
353
|
-
if (context.toolName === BASH_TOOL_NAME) {
|
|
354
|
-
return {
|
|
355
|
-
behavior: "deny",
|
|
356
|
-
message: "Bash commands are not allowed in plan mode.",
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
|
|
360
350
|
const writeTools = [
|
|
361
351
|
EDIT_TOOL_NAME,
|
|
362
352
|
MULTI_EDIT_TOOL_NAME,
|
|
@@ -492,6 +482,27 @@ export class PermissionManager {
|
|
|
492
482
|
suggestedPrefix,
|
|
493
483
|
};
|
|
494
484
|
|
|
485
|
+
// Set hidePersistentOption for out-of-bounds file operations
|
|
486
|
+
const fileTools = [
|
|
487
|
+
EDIT_TOOL_NAME,
|
|
488
|
+
MULTI_EDIT_TOOL_NAME,
|
|
489
|
+
DELETE_FILE_TOOL_NAME,
|
|
490
|
+
WRITE_TOOL_NAME,
|
|
491
|
+
];
|
|
492
|
+
if (fileTools.includes(toolName)) {
|
|
493
|
+
const targetPath = (toolInput?.file_path || toolInput?.target_file) as
|
|
494
|
+
| string
|
|
495
|
+
| undefined;
|
|
496
|
+
const workdir = toolInput?.workdir as string | undefined;
|
|
497
|
+
|
|
498
|
+
if (targetPath) {
|
|
499
|
+
const { isInside } = this.isInsideSafeZone(targetPath, workdir);
|
|
500
|
+
if (!isInside) {
|
|
501
|
+
context.hidePersistentOption = true;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
495
506
|
// Set hidePersistentOption for dangerous or out-of-bounds bash commands
|
|
496
507
|
if (toolName === BASH_TOOL_NAME && toolInput?.command) {
|
|
497
508
|
const command = String(toolInput.command);
|
|
@@ -17,7 +17,7 @@ export class PlanManager {
|
|
|
17
17
|
/**
|
|
18
18
|
* Ensures the plan directory exists and generates a new plan file path with a random name
|
|
19
19
|
*/
|
|
20
|
-
public async getOrGeneratePlanFilePath(): Promise<{
|
|
20
|
+
public async getOrGeneratePlanFilePath(seed?: string): Promise<{
|
|
21
21
|
path: string;
|
|
22
22
|
name: string;
|
|
23
23
|
}> {
|
|
@@ -30,7 +30,7 @@ export class PlanManager {
|
|
|
30
30
|
);
|
|
31
31
|
throw error;
|
|
32
32
|
}
|
|
33
|
-
const name = generateRandomName();
|
|
33
|
+
const name = generateRandomName(seed);
|
|
34
34
|
const filePath = path.join(this.planDir, `${name}.md`);
|
|
35
35
|
this.logger?.info(`Generated plan file path: ${filePath}`);
|
|
36
36
|
return { path: filePath, name };
|