wave-agent-sdk 0.0.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/README.md +32 -0
- package/dist/agent.d.ts +96 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +286 -0
- package/dist/hooks/executor.d.ts +56 -0
- package/dist/hooks/executor.d.ts.map +1 -0
- package/dist/hooks/executor.js +312 -0
- package/dist/hooks/index.d.ts +17 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +14 -0
- package/dist/hooks/manager.d.ts +90 -0
- package/dist/hooks/manager.d.ts.map +1 -0
- package/dist/hooks/manager.js +395 -0
- package/dist/hooks/matcher.d.ts +49 -0
- package/dist/hooks/matcher.d.ts.map +1 -0
- package/dist/hooks/matcher.js +147 -0
- package/dist/hooks/settings.d.ts +46 -0
- package/dist/hooks/settings.d.ts.map +1 -0
- package/dist/hooks/settings.js +100 -0
- package/dist/hooks/types.d.ts +80 -0
- package/dist/hooks/types.d.ts.map +1 -0
- package/dist/hooks/types.js +59 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/managers/aiManager.d.ts +61 -0
- package/dist/managers/aiManager.d.ts.map +1 -0
- package/dist/managers/aiManager.js +415 -0
- package/dist/managers/backgroundBashManager.d.ts +27 -0
- package/dist/managers/backgroundBashManager.d.ts.map +1 -0
- package/dist/managers/backgroundBashManager.js +166 -0
- package/dist/managers/bashManager.d.ts +20 -0
- package/dist/managers/bashManager.d.ts.map +1 -0
- package/dist/managers/bashManager.js +66 -0
- package/dist/managers/mcpManager.d.ts +63 -0
- package/dist/managers/mcpManager.d.ts.map +1 -0
- package/dist/managers/mcpManager.js +378 -0
- package/dist/managers/messageManager.d.ts +85 -0
- package/dist/managers/messageManager.d.ts.map +1 -0
- package/dist/managers/messageManager.js +265 -0
- package/dist/managers/skillManager.d.ts +59 -0
- package/dist/managers/skillManager.d.ts.map +1 -0
- package/dist/managers/skillManager.js +317 -0
- package/dist/managers/slashCommandManager.d.ts +77 -0
- package/dist/managers/slashCommandManager.d.ts.map +1 -0
- package/dist/managers/slashCommandManager.js +208 -0
- package/dist/managers/toolManager.d.ts +23 -0
- package/dist/managers/toolManager.d.ts.map +1 -0
- package/dist/managers/toolManager.js +79 -0
- package/dist/services/aiService.d.ts +28 -0
- package/dist/services/aiService.d.ts.map +1 -0
- package/dist/services/aiService.js +180 -0
- package/dist/services/memory.d.ts +8 -0
- package/dist/services/memory.d.ts.map +1 -0
- package/dist/services/memory.js +128 -0
- package/dist/services/session.d.ts +54 -0
- package/dist/services/session.d.ts.map +1 -0
- package/dist/services/session.js +196 -0
- package/dist/tools/bashTool.d.ts +14 -0
- package/dist/tools/bashTool.d.ts.map +1 -0
- package/dist/tools/bashTool.js +351 -0
- package/dist/tools/deleteFileTool.d.ts +6 -0
- package/dist/tools/deleteFileTool.d.ts.map +1 -0
- package/dist/tools/deleteFileTool.js +67 -0
- package/dist/tools/editTool.d.ts +6 -0
- package/dist/tools/editTool.d.ts.map +1 -0
- package/dist/tools/editTool.js +168 -0
- package/dist/tools/globTool.d.ts +6 -0
- package/dist/tools/globTool.d.ts.map +1 -0
- package/dist/tools/globTool.js +113 -0
- package/dist/tools/grepTool.d.ts +6 -0
- package/dist/tools/grepTool.d.ts.map +1 -0
- package/dist/tools/grepTool.js +268 -0
- package/dist/tools/lsTool.d.ts +6 -0
- package/dist/tools/lsTool.d.ts.map +1 -0
- package/dist/tools/lsTool.js +160 -0
- package/dist/tools/multiEditTool.d.ts +6 -0
- package/dist/tools/multiEditTool.d.ts.map +1 -0
- package/dist/tools/multiEditTool.js +222 -0
- package/dist/tools/readTool.d.ts +6 -0
- package/dist/tools/readTool.d.ts.map +1 -0
- package/dist/tools/readTool.js +136 -0
- package/dist/tools/types.d.ts +35 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +4 -0
- package/dist/tools/writeTool.d.ts +6 -0
- package/dist/tools/writeTool.d.ts.map +1 -0
- package/dist/tools/writeTool.js +138 -0
- package/dist/types.d.ts +212 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/dist/utils/bashHistory.d.ts +46 -0
- package/dist/utils/bashHistory.d.ts.map +1 -0
- package/dist/utils/bashHistory.js +236 -0
- package/dist/utils/commandArgumentParser.d.ts +34 -0
- package/dist/utils/commandArgumentParser.d.ts.map +1 -0
- package/dist/utils/commandArgumentParser.js +123 -0
- package/dist/utils/constants.d.ts +27 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +28 -0
- package/dist/utils/convertMessagesForAPI.d.ts +9 -0
- package/dist/utils/convertMessagesForAPI.d.ts.map +1 -0
- package/dist/utils/convertMessagesForAPI.js +189 -0
- package/dist/utils/customCommands.d.ts +14 -0
- package/dist/utils/customCommands.d.ts.map +1 -0
- package/dist/utils/customCommands.js +71 -0
- package/dist/utils/fileFilter.d.ts +26 -0
- package/dist/utils/fileFilter.d.ts.map +1 -0
- package/dist/utils/fileFilter.js +177 -0
- package/dist/utils/markdownParser.d.ts +27 -0
- package/dist/utils/markdownParser.d.ts.map +1 -0
- package/dist/utils/markdownParser.js +109 -0
- package/dist/utils/mcpUtils.d.ts +24 -0
- package/dist/utils/mcpUtils.d.ts.map +1 -0
- package/dist/utils/mcpUtils.js +51 -0
- package/dist/utils/messageOperations.d.ts +118 -0
- package/dist/utils/messageOperations.d.ts.map +1 -0
- package/dist/utils/messageOperations.js +334 -0
- package/dist/utils/path.d.ts +25 -0
- package/dist/utils/path.d.ts.map +1 -0
- package/dist/utils/path.js +109 -0
- package/dist/utils/skillParser.d.ts +18 -0
- package/dist/utils/skillParser.d.ts.map +1 -0
- package/dist/utils/skillParser.js +147 -0
- package/dist/utils/stringUtils.d.ts +13 -0
- package/dist/utils/stringUtils.d.ts.map +1 -0
- package/dist/utils/stringUtils.js +44 -0
- package/package.json +51 -0
- package/src/agent.ts +405 -0
- package/src/hooks/executor.ts +440 -0
- package/src/hooks/index.ts +52 -0
- package/src/hooks/manager.ts +618 -0
- package/src/hooks/matcher.ts +187 -0
- package/src/hooks/settings.ts +129 -0
- package/src/hooks/types.ts +169 -0
- package/src/index.ts +24 -0
- package/src/managers/aiManager.ts +573 -0
- package/src/managers/backgroundBashManager.ts +203 -0
- package/src/managers/bashManager.ts +97 -0
- package/src/managers/mcpManager.ts +493 -0
- package/src/managers/messageManager.ts +415 -0
- package/src/managers/skillManager.ts +404 -0
- package/src/managers/slashCommandManager.ts +293 -0
- package/src/managers/toolManager.ts +106 -0
- package/src/services/aiService.ts +252 -0
- package/src/services/memory.ts +149 -0
- package/src/services/session.ts +265 -0
- package/src/tools/bashTool.ts +402 -0
- package/src/tools/deleteFileTool.ts +81 -0
- package/src/tools/editTool.ts +192 -0
- package/src/tools/globTool.ts +135 -0
- package/src/tools/grepTool.ts +326 -0
- package/src/tools/lsTool.ts +187 -0
- package/src/tools/multiEditTool.ts +268 -0
- package/src/tools/readTool.ts +165 -0
- package/src/tools/types.ts +47 -0
- package/src/tools/writeTool.ts +163 -0
- package/src/types.ts +260 -0
- package/src/utils/bashHistory.ts +303 -0
- package/src/utils/commandArgumentParser.ts +153 -0
- package/src/utils/constants.ts +37 -0
- package/src/utils/convertMessagesForAPI.ts +236 -0
- package/src/utils/customCommands.ts +85 -0
- package/src/utils/fileFilter.ts +202 -0
- package/src/utils/markdownParser.ts +156 -0
- package/src/utils/mcpUtils.ts +81 -0
- package/src/utils/messageOperations.ts +506 -0
- package/src/utils/path.ts +118 -0
- package/src/utils/skillParser.ts +188 -0
- package/src/utils/stringUtils.ts +50 -0
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
import { callAgent, compressMessages } from "../services/aiService.js";
|
|
2
|
+
import { getMessagesToCompress } from "../utils/messageOperations.js";
|
|
3
|
+
import { convertMessagesForAPI } from "../utils/convertMessagesForAPI.js";
|
|
4
|
+
import * as memory from "../services/memory.js";
|
|
5
|
+
import type { Logger } from "../types.js";
|
|
6
|
+
import type { ToolManager } from "./toolManager.js";
|
|
7
|
+
import type { ToolContext, ToolResult } from "../tools/types.js";
|
|
8
|
+
import type { MessageManager } from "./messageManager.js";
|
|
9
|
+
import type { BackgroundBashManager } from "./backgroundBashManager.js";
|
|
10
|
+
import { DEFAULT_TOKEN_LIMIT } from "../utils/constants.js";
|
|
11
|
+
import { ChatCompletionMessageFunctionToolCall } from "openai/resources.js";
|
|
12
|
+
import type { HookManager } from "../hooks/index.js";
|
|
13
|
+
import type { ExtendedHookExecutionContext } from "../hooks/types.js";
|
|
14
|
+
|
|
15
|
+
export interface AIManagerCallbacks {
|
|
16
|
+
onCompressionStateChange?: (isCompressing: boolean) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface AIManagerOptions {
|
|
20
|
+
messageManager: MessageManager;
|
|
21
|
+
toolManager: ToolManager;
|
|
22
|
+
logger?: Logger;
|
|
23
|
+
backgroundBashManager?: BackgroundBashManager;
|
|
24
|
+
hookManager?: HookManager;
|
|
25
|
+
callbacks?: AIManagerCallbacks;
|
|
26
|
+
workdir: string;
|
|
27
|
+
systemPrompt?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class AIManager {
|
|
31
|
+
public isLoading: boolean = false;
|
|
32
|
+
private abortController: AbortController | null = null;
|
|
33
|
+
private toolAbortController: AbortController | null = null;
|
|
34
|
+
private logger?: Logger;
|
|
35
|
+
private toolManager: ToolManager;
|
|
36
|
+
private messageManager: MessageManager;
|
|
37
|
+
private backgroundBashManager?: BackgroundBashManager;
|
|
38
|
+
private hookManager?: HookManager;
|
|
39
|
+
private workdir: string;
|
|
40
|
+
private systemPrompt?: string;
|
|
41
|
+
|
|
42
|
+
constructor(options: AIManagerOptions) {
|
|
43
|
+
this.messageManager = options.messageManager;
|
|
44
|
+
this.toolManager = options.toolManager;
|
|
45
|
+
this.backgroundBashManager = options.backgroundBashManager;
|
|
46
|
+
this.hookManager = options.hookManager;
|
|
47
|
+
this.logger = options.logger;
|
|
48
|
+
this.workdir = options.workdir;
|
|
49
|
+
this.systemPrompt = options.systemPrompt;
|
|
50
|
+
this.callbacks = options.callbacks ?? {};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private isCompressing: boolean = false;
|
|
54
|
+
private callbacks: AIManagerCallbacks;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get filtered tool configuration
|
|
58
|
+
*/
|
|
59
|
+
private getFilteredToolsConfig(allowedTools?: string[]) {
|
|
60
|
+
const allTools = this.toolManager.getToolsConfig();
|
|
61
|
+
|
|
62
|
+
// If no allowedTools specified, return all tools
|
|
63
|
+
if (!allowedTools || allowedTools.length === 0) {
|
|
64
|
+
return allTools;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Filter allowed tools
|
|
68
|
+
return allTools.filter((tool) => allowedTools.includes(tool.function.name));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public setIsLoading(isLoading: boolean): void {
|
|
72
|
+
this.isLoading = isLoading;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public abortAIMessage(): void {
|
|
76
|
+
// Interrupt AI service
|
|
77
|
+
if (this.abortController) {
|
|
78
|
+
try {
|
|
79
|
+
this.abortController.abort();
|
|
80
|
+
} catch (error) {
|
|
81
|
+
this.logger?.error("Failed to abort AI service:", error);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Interrupt tool execution
|
|
86
|
+
if (this.toolAbortController) {
|
|
87
|
+
try {
|
|
88
|
+
this.toolAbortController.abort();
|
|
89
|
+
} catch (error) {
|
|
90
|
+
this.logger?.error("Failed to abort tool execution:", error);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
this.setIsLoading(false);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Helper method to generate compactParams
|
|
98
|
+
private generateCompactParams(
|
|
99
|
+
toolName: string,
|
|
100
|
+
toolArgs: Record<string, unknown>,
|
|
101
|
+
): string | undefined {
|
|
102
|
+
try {
|
|
103
|
+
const toolPlugin = this.toolManager
|
|
104
|
+
.list()
|
|
105
|
+
.find((plugin) => plugin.name === toolName);
|
|
106
|
+
if (toolPlugin?.formatCompactParams) {
|
|
107
|
+
const context: ToolContext = {
|
|
108
|
+
workdir: this.workdir,
|
|
109
|
+
};
|
|
110
|
+
return toolPlugin.formatCompactParams(toolArgs, context);
|
|
111
|
+
}
|
|
112
|
+
} catch (error) {
|
|
113
|
+
this.logger?.warn("Failed to generate compactParams", error);
|
|
114
|
+
}
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Private method to handle token statistics and message compression
|
|
119
|
+
private async handleTokenUsageAndCompression(
|
|
120
|
+
usage: { total_tokens: number } | undefined,
|
|
121
|
+
abortController: AbortController,
|
|
122
|
+
): Promise<void> {
|
|
123
|
+
if (!usage) return;
|
|
124
|
+
|
|
125
|
+
// Update token statistics - display latest token usage
|
|
126
|
+
this.messageManager.setlatestTotalTokens(usage.total_tokens);
|
|
127
|
+
|
|
128
|
+
// Check if token limit exceeded
|
|
129
|
+
const tokenLimit = parseInt(
|
|
130
|
+
process.env.TOKEN_LIMIT || `${DEFAULT_TOKEN_LIMIT}`,
|
|
131
|
+
10,
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
if (usage.total_tokens > tokenLimit) {
|
|
135
|
+
this.logger?.info(
|
|
136
|
+
`Token usage exceeded ${tokenLimit}, compressing messages...`,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// Check if messages need compression
|
|
140
|
+
const { messagesToCompress, insertIndex } = getMessagesToCompress(
|
|
141
|
+
this.messageManager.getMessages(),
|
|
142
|
+
7,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// If there are messages to compress, perform compression
|
|
146
|
+
if (messagesToCompress.length > 0) {
|
|
147
|
+
const recentChatMessages = convertMessagesForAPI(messagesToCompress);
|
|
148
|
+
|
|
149
|
+
this.setIsCompressing(true);
|
|
150
|
+
try {
|
|
151
|
+
const compressedContent = await compressMessages({
|
|
152
|
+
messages: recentChatMessages,
|
|
153
|
+
abortSignal: abortController.signal,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Execute message reconstruction and sessionId update after compression
|
|
157
|
+
this.messageManager.compressMessagesAndUpdateSession(
|
|
158
|
+
insertIndex,
|
|
159
|
+
compressedContent,
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
this.logger?.info(
|
|
163
|
+
`Successfully compressed ${messagesToCompress.length} messages and updated session`,
|
|
164
|
+
);
|
|
165
|
+
} catch (compressError) {
|
|
166
|
+
this.logger?.error("Failed to compress messages:", compressError);
|
|
167
|
+
} finally {
|
|
168
|
+
this.setIsCompressing(false);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
public getIsCompressing(): boolean {
|
|
175
|
+
return this.isCompressing;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
public setIsCompressing(isCompressing: boolean): void {
|
|
179
|
+
if (this.isCompressing !== isCompressing) {
|
|
180
|
+
this.isCompressing = isCompressing;
|
|
181
|
+
this.callbacks.onCompressionStateChange?.(isCompressing);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
public async sendAIMessage(
|
|
186
|
+
options: {
|
|
187
|
+
recursionDepth?: number;
|
|
188
|
+
model?: string;
|
|
189
|
+
allowedTools?: string[];
|
|
190
|
+
} = {},
|
|
191
|
+
): Promise<void> {
|
|
192
|
+
const { recursionDepth = 0, model, allowedTools } = options;
|
|
193
|
+
// Only check isLoading for the initial call (recursionDepth === 0)
|
|
194
|
+
if (recursionDepth === 0 && this.isLoading) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Create new AbortController
|
|
199
|
+
const abortController = new AbortController();
|
|
200
|
+
this.abortController = abortController;
|
|
201
|
+
|
|
202
|
+
// Create separate AbortController for tool execution
|
|
203
|
+
const toolAbortController = new AbortController();
|
|
204
|
+
this.toolAbortController = toolAbortController;
|
|
205
|
+
|
|
206
|
+
// Only set loading state for the initial call
|
|
207
|
+
if (recursionDepth === 0) {
|
|
208
|
+
this.setIsLoading(true);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Get recent message history
|
|
212
|
+
const recentMessages = convertMessagesForAPI(
|
|
213
|
+
this.messageManager.getMessages(),
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
// Get combined memory content
|
|
218
|
+
const combinedMemory = await memory.getCombinedMemoryContent(
|
|
219
|
+
this.workdir,
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
// Call AI service (non-streaming)
|
|
223
|
+
const result = await callAgent({
|
|
224
|
+
messages: recentMessages,
|
|
225
|
+
sessionId: this.messageManager.getSessionId(),
|
|
226
|
+
abortSignal: abortController.signal,
|
|
227
|
+
memory: combinedMemory, // Pass combined memory content
|
|
228
|
+
workdir: this.workdir, // Pass working directory
|
|
229
|
+
tools: this.getFilteredToolsConfig(allowedTools), // Pass filtered tool configuration
|
|
230
|
+
model: model, // Use passed model
|
|
231
|
+
systemPrompt: this.systemPrompt, // Pass custom system prompt
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Collect content and tool calls
|
|
235
|
+
const content = result.content || "";
|
|
236
|
+
const toolCalls: ChatCompletionMessageFunctionToolCall[] = [];
|
|
237
|
+
|
|
238
|
+
if (result.tool_calls) {
|
|
239
|
+
for (const toolCall of result.tool_calls) {
|
|
240
|
+
if (toolCall.type === "function") {
|
|
241
|
+
toolCalls.push(toolCall);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Add assistant message at once (including content and tool calls)
|
|
247
|
+
this.messageManager.addAssistantMessage(content, toolCalls);
|
|
248
|
+
|
|
249
|
+
if (toolCalls.length > 0) {
|
|
250
|
+
for (const functionToolCall of toolCalls) {
|
|
251
|
+
const toolId = functionToolCall.id || "";
|
|
252
|
+
// Execute tool
|
|
253
|
+
try {
|
|
254
|
+
// Check if already interrupted, skip tool execution if so
|
|
255
|
+
if (
|
|
256
|
+
abortController.signal.aborted ||
|
|
257
|
+
toolAbortController.signal.aborted
|
|
258
|
+
) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Safely parse tool parameters, handle tools without parameters
|
|
263
|
+
let toolArgs: Record<string, unknown> = {};
|
|
264
|
+
const argsString = functionToolCall.function?.arguments?.trim();
|
|
265
|
+
|
|
266
|
+
if (!argsString || argsString === "") {
|
|
267
|
+
// Tool without parameters, use empty object
|
|
268
|
+
toolArgs = {};
|
|
269
|
+
} else {
|
|
270
|
+
try {
|
|
271
|
+
toolArgs = JSON.parse(argsString);
|
|
272
|
+
} catch (parseError) {
|
|
273
|
+
// For non-empty but malformed JSON, still throw exception
|
|
274
|
+
const errorMessage = `Failed to parse tool arguments: ${argsString}`;
|
|
275
|
+
this.logger?.error(errorMessage, parseError);
|
|
276
|
+
throw new Error(errorMessage);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Set tool start execution state
|
|
281
|
+
const toolName = functionToolCall.function?.name || "";
|
|
282
|
+
const compactParams = this.generateCompactParams(
|
|
283
|
+
toolName,
|
|
284
|
+
toolArgs,
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
this.messageManager.updateToolBlock({
|
|
288
|
+
toolId,
|
|
289
|
+
args: JSON.stringify(toolArgs, null, 2),
|
|
290
|
+
isRunning: true, // isRunning: true
|
|
291
|
+
name: toolName,
|
|
292
|
+
compactParams,
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
try {
|
|
296
|
+
// Execute PreToolUse hooks before tool execution
|
|
297
|
+
await this.executePreToolUseHooks(toolName, toolArgs);
|
|
298
|
+
|
|
299
|
+
// Create tool execution context
|
|
300
|
+
const context: ToolContext = {
|
|
301
|
+
abortSignal: toolAbortController.signal,
|
|
302
|
+
backgroundBashManager: this.backgroundBashManager,
|
|
303
|
+
workdir: this.workdir,
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
// Execute tool
|
|
307
|
+
const toolResult = await this.toolManager.execute(
|
|
308
|
+
functionToolCall.function?.name || "",
|
|
309
|
+
toolArgs,
|
|
310
|
+
context,
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
// Update message state - tool execution completed
|
|
314
|
+
this.messageManager.updateToolBlock({
|
|
315
|
+
toolId,
|
|
316
|
+
args: JSON.stringify(toolArgs, null, 2),
|
|
317
|
+
result:
|
|
318
|
+
toolResult.content ||
|
|
319
|
+
(toolResult.error ? `Error: ${toolResult.error}` : ""),
|
|
320
|
+
success: toolResult.success,
|
|
321
|
+
error: toolResult.error,
|
|
322
|
+
isRunning: false, // isRunning: false
|
|
323
|
+
name: toolName,
|
|
324
|
+
shortResult: toolResult.shortResult,
|
|
325
|
+
compactParams,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// If tool returns diff information, add diff block
|
|
329
|
+
if (
|
|
330
|
+
toolResult.success &&
|
|
331
|
+
toolResult.diffResult &&
|
|
332
|
+
toolResult.filePath
|
|
333
|
+
) {
|
|
334
|
+
this.messageManager.addDiffBlock(
|
|
335
|
+
toolResult.filePath,
|
|
336
|
+
toolResult.diffResult,
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Execute PostToolUse hooks after successful tool completion
|
|
341
|
+
await this.executePostToolUseHooks(
|
|
342
|
+
toolName,
|
|
343
|
+
toolArgs,
|
|
344
|
+
toolResult,
|
|
345
|
+
);
|
|
346
|
+
} catch (toolError) {
|
|
347
|
+
const errorMessage =
|
|
348
|
+
toolError instanceof Error
|
|
349
|
+
? toolError.message
|
|
350
|
+
: String(toolError);
|
|
351
|
+
|
|
352
|
+
this.messageManager.updateToolBlock({
|
|
353
|
+
toolId,
|
|
354
|
+
args: JSON.stringify(toolArgs, null, 2),
|
|
355
|
+
result: `Tool execution failed: ${errorMessage}`,
|
|
356
|
+
success: false,
|
|
357
|
+
error: errorMessage,
|
|
358
|
+
isRunning: false,
|
|
359
|
+
name: toolName,
|
|
360
|
+
compactParams,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
} catch (parseError) {
|
|
364
|
+
// Check if it's a parsing error due to interruption
|
|
365
|
+
const isAborted =
|
|
366
|
+
abortController.signal.aborted ||
|
|
367
|
+
toolAbortController.signal.aborted;
|
|
368
|
+
|
|
369
|
+
if (isAborted) {
|
|
370
|
+
// If interrupted, return directly without showing error
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const errorMessage =
|
|
375
|
+
parseError instanceof Error
|
|
376
|
+
? parseError.message
|
|
377
|
+
: String(parseError);
|
|
378
|
+
this.messageManager.addErrorBlock(
|
|
379
|
+
`Failed to parse tool arguments for ${functionToolCall.function?.name}: ${errorMessage}`,
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Handle token statistics and message compression
|
|
386
|
+
await this.handleTokenUsageAndCompression(result.usage, abortController);
|
|
387
|
+
|
|
388
|
+
// Check if there are tool operations, if so automatically initiate next AI service call
|
|
389
|
+
if (toolCalls.length > 0) {
|
|
390
|
+
// Check interruption status
|
|
391
|
+
const isCurrentlyAborted =
|
|
392
|
+
abortController.signal.aborted || toolAbortController.signal.aborted;
|
|
393
|
+
|
|
394
|
+
// AI service call ends, clear abort controller
|
|
395
|
+
this.abortController = null;
|
|
396
|
+
|
|
397
|
+
// Clear tool AbortController after tool execution completes
|
|
398
|
+
this.toolAbortController = null;
|
|
399
|
+
|
|
400
|
+
if (!isCurrentlyAborted) {
|
|
401
|
+
// Recursively call AI service, increment recursion depth, and pass same configuration
|
|
402
|
+
await this.sendAIMessage({
|
|
403
|
+
recursionDepth: recursionDepth + 1,
|
|
404
|
+
model,
|
|
405
|
+
allowedTools,
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
} else {
|
|
409
|
+
// Clear abort controller when no tool operations
|
|
410
|
+
this.abortController = null;
|
|
411
|
+
this.toolAbortController = null;
|
|
412
|
+
}
|
|
413
|
+
} catch (error) {
|
|
414
|
+
// Check if error is due to user interrupt operation
|
|
415
|
+
const isAborted =
|
|
416
|
+
abortController.signal.aborted ||
|
|
417
|
+
toolAbortController.signal.aborted ||
|
|
418
|
+
(error instanceof Error &&
|
|
419
|
+
(error.name === "AbortError" || error.message.includes("aborted")));
|
|
420
|
+
|
|
421
|
+
if (!isAborted) {
|
|
422
|
+
this.messageManager.addErrorBlock(
|
|
423
|
+
error instanceof Error ? error.message : "Unknown error occurred",
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Reset abort controller on error
|
|
428
|
+
this.abortController = null;
|
|
429
|
+
this.toolAbortController = null;
|
|
430
|
+
} finally {
|
|
431
|
+
// Only clear loading state for the initial call
|
|
432
|
+
if (recursionDepth === 0) {
|
|
433
|
+
this.setIsLoading(false);
|
|
434
|
+
|
|
435
|
+
// Save session before executing Stop hooks
|
|
436
|
+
await this.messageManager.saveSession();
|
|
437
|
+
|
|
438
|
+
// Execute Stop hooks when AI response cycle completes
|
|
439
|
+
await this.executeStopHooks();
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Execute Stop hooks when AI response cycle completes
|
|
446
|
+
*/
|
|
447
|
+
private async executeStopHooks(): Promise<void> {
|
|
448
|
+
if (!this.hookManager) return;
|
|
449
|
+
|
|
450
|
+
try {
|
|
451
|
+
const context: ExtendedHookExecutionContext = {
|
|
452
|
+
event: "Stop",
|
|
453
|
+
projectDir: this.workdir,
|
|
454
|
+
timestamp: new Date(),
|
|
455
|
+
sessionId: this.messageManager.getSessionId(),
|
|
456
|
+
transcriptPath: this.messageManager.getTranscriptPath(),
|
|
457
|
+
cwd: this.workdir,
|
|
458
|
+
// Stop hooks don't need toolName, toolInput, toolResponse, or userPrompt
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
const results = await this.hookManager.executeHooks("Stop", context);
|
|
462
|
+
|
|
463
|
+
// Log hook execution results for debugging
|
|
464
|
+
if (results.length > 0) {
|
|
465
|
+
this.logger?.debug(
|
|
466
|
+
`Executed ${results.length} Stop hook(s):`,
|
|
467
|
+
results.map((r) => ({
|
|
468
|
+
success: r.success,
|
|
469
|
+
duration: r.duration,
|
|
470
|
+
exitCode: r.exitCode,
|
|
471
|
+
timedOut: r.timedOut,
|
|
472
|
+
stderr: r.stderr,
|
|
473
|
+
})),
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
} catch (error) {
|
|
477
|
+
// Hook execution errors should not interrupt the main workflow
|
|
478
|
+
this.logger?.error("Stop hook execution failed:", error);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Execute PreToolUse hooks before tool execution
|
|
484
|
+
*/
|
|
485
|
+
private async executePreToolUseHooks(
|
|
486
|
+
toolName: string,
|
|
487
|
+
toolInput?: Record<string, unknown>,
|
|
488
|
+
): Promise<void> {
|
|
489
|
+
if (!this.hookManager) return;
|
|
490
|
+
|
|
491
|
+
try {
|
|
492
|
+
const context: ExtendedHookExecutionContext = {
|
|
493
|
+
event: "PreToolUse",
|
|
494
|
+
projectDir: this.workdir,
|
|
495
|
+
timestamp: new Date(),
|
|
496
|
+
toolName,
|
|
497
|
+
sessionId: this.messageManager.getSessionId(),
|
|
498
|
+
transcriptPath: this.messageManager.getTranscriptPath(),
|
|
499
|
+
cwd: this.workdir,
|
|
500
|
+
toolInput,
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
const results = await this.hookManager.executeHooks(
|
|
504
|
+
"PreToolUse",
|
|
505
|
+
context,
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
// Log hook execution results for debugging
|
|
509
|
+
if (results.length > 0) {
|
|
510
|
+
this.logger?.debug(
|
|
511
|
+
`Executed ${results.length} PreToolUse hook(s) for ${toolName}:`,
|
|
512
|
+
results.map((r) => ({
|
|
513
|
+
success: r.success,
|
|
514
|
+
duration: r.duration,
|
|
515
|
+
exitCode: r.exitCode,
|
|
516
|
+
timedOut: r.timedOut,
|
|
517
|
+
stderr: r.stderr,
|
|
518
|
+
})),
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
} catch (error) {
|
|
522
|
+
// Hook execution errors should not interrupt the main workflow
|
|
523
|
+
this.logger?.error("PreToolUse hook execution failed:", error);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Execute PostToolUse hooks after tool completion
|
|
529
|
+
*/
|
|
530
|
+
private async executePostToolUseHooks(
|
|
531
|
+
toolName: string,
|
|
532
|
+
toolInput?: Record<string, unknown>,
|
|
533
|
+
toolResponse?: ToolResult,
|
|
534
|
+
): Promise<void> {
|
|
535
|
+
if (!this.hookManager) return;
|
|
536
|
+
|
|
537
|
+
try {
|
|
538
|
+
const context: ExtendedHookExecutionContext = {
|
|
539
|
+
event: "PostToolUse",
|
|
540
|
+
projectDir: this.workdir,
|
|
541
|
+
timestamp: new Date(),
|
|
542
|
+
toolName,
|
|
543
|
+
sessionId: this.messageManager.getSessionId(),
|
|
544
|
+
transcriptPath: this.messageManager.getTranscriptPath(),
|
|
545
|
+
cwd: this.workdir,
|
|
546
|
+
toolInput,
|
|
547
|
+
toolResponse,
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
const results = await this.hookManager.executeHooks(
|
|
551
|
+
"PostToolUse",
|
|
552
|
+
context,
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
// Log hook execution results for debugging
|
|
556
|
+
if (results.length > 0) {
|
|
557
|
+
this.logger?.debug(
|
|
558
|
+
`Executed ${results.length} PostToolUse hook(s) for ${toolName}:`,
|
|
559
|
+
results.map((r) => ({
|
|
560
|
+
success: r.success,
|
|
561
|
+
duration: r.duration,
|
|
562
|
+
exitCode: r.exitCode,
|
|
563
|
+
timedOut: r.timedOut,
|
|
564
|
+
stderr: r.stderr,
|
|
565
|
+
})),
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
} catch (error) {
|
|
569
|
+
// Hook execution errors should not interrupt the main workflow
|
|
570
|
+
this.logger?.error("PostToolUse hook execution failed:", error);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|