wave-agent-sdk 0.0.4 → 0.0.6
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 +63 -9
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +103 -27
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/managers/aiManager.d.ts +5 -2
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +121 -53
- package/dist/managers/backgroundBashManager.d.ts +1 -1
- package/dist/managers/backgroundBashManager.d.ts.map +1 -1
- package/dist/{hooks/manager.d.ts → managers/hookManager.d.ts} +26 -7
- package/dist/managers/hookManager.d.ts.map +1 -0
- package/dist/{hooks/manager.js → managers/hookManager.js} +108 -18
- package/dist/managers/mcpManager.d.ts +1 -1
- package/dist/managers/mcpManager.d.ts.map +1 -1
- package/dist/managers/mcpManager.js +5 -5
- package/dist/managers/messageManager.d.ts +29 -5
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +33 -12
- package/dist/managers/skillManager.d.ts +1 -1
- package/dist/managers/skillManager.d.ts.map +1 -1
- package/dist/managers/skillManager.js +3 -3
- package/dist/managers/slashCommandManager.d.ts +1 -1
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +1 -1
- package/dist/managers/subagentManager.d.ts +9 -12
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +43 -45
- package/dist/managers/toolManager.d.ts +1 -1
- package/dist/managers/toolManager.d.ts.map +1 -1
- package/dist/services/aiService.d.ts +10 -2
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +25 -4
- package/dist/services/hook.d.ts +56 -0
- package/dist/services/hook.d.ts.map +1 -0
- package/dist/services/hook.js +276 -0
- package/dist/services/memory.js +3 -3
- package/dist/services/session.d.ts +65 -16
- package/dist/services/session.d.ts.map +1 -1
- package/dist/services/session.js +85 -34
- package/dist/tools/bashTool.js +2 -2
- package/dist/tools/deleteFileTool.js +1 -1
- package/dist/tools/editTool.js +1 -1
- package/dist/tools/multiEditTool.js +2 -2
- package/dist/tools/taskTool.d.ts.map +1 -1
- package/dist/tools/taskTool.js +7 -3
- package/dist/tools/writeTool.js +1 -1
- package/dist/types/commands.d.ts +24 -0
- package/dist/types/commands.d.ts.map +1 -0
- package/dist/types/commands.js +5 -0
- package/dist/types/config.d.ts +13 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +5 -0
- package/dist/types/core.d.ts +38 -0
- package/dist/types/core.d.ts.map +1 -0
- package/dist/{types.js → types/core.js} +4 -13
- package/dist/{hooks/types.d.ts → types/hooks.d.ts} +2 -1
- package/dist/types/hooks.d.ts.map +1 -0
- package/dist/types/index.d.ts +20 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +21 -0
- package/dist/types/mcp.d.ts +28 -0
- package/dist/types/mcp.d.ts.map +1 -0
- package/dist/types/mcp.js +5 -0
- package/dist/types/messaging.d.ts +80 -0
- package/dist/types/messaging.d.ts.map +1 -0
- package/dist/types/messaging.js +5 -0
- package/dist/types/processes.d.ts +17 -0
- package/dist/types/processes.d.ts.map +1 -0
- package/dist/types/processes.js +5 -0
- package/dist/types/skills.d.ts +78 -0
- package/dist/types/skills.d.ts.map +1 -0
- package/dist/types/skills.js +17 -0
- package/dist/utils/configResolver.d.ts +1 -1
- package/dist/utils/configResolver.d.ts.map +1 -1
- package/dist/utils/configResolver.js +1 -1
- package/dist/utils/configValidator.d.ts +1 -1
- package/dist/utils/configValidator.d.ts.map +1 -1
- package/dist/utils/configValidator.js +1 -1
- package/dist/utils/convertMessagesForAPI.d.ts +1 -1
- package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
- package/dist/utils/customCommands.d.ts +1 -1
- package/dist/utils/customCommands.d.ts.map +1 -1
- package/dist/{hooks/matcher.d.ts → utils/hookMatcher.d.ts} +1 -1
- package/dist/utils/hookMatcher.d.ts.map +1 -0
- package/dist/utils/markdownParser.d.ts +1 -1
- package/dist/utils/markdownParser.d.ts.map +1 -1
- package/dist/utils/mcpUtils.d.ts +1 -1
- package/dist/utils/mcpUtils.d.ts.map +1 -1
- package/dist/utils/messageOperations.d.ts +7 -2
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +18 -1
- package/dist/utils/skillParser.d.ts +1 -1
- package/dist/utils/skillParser.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/agent.ts +150 -50
- package/src/index.ts +3 -4
- package/src/managers/aiManager.ts +282 -164
- package/src/managers/backgroundBashManager.ts +1 -1
- package/src/{hooks/manager.ts → managers/hookManager.ts} +163 -28
- package/src/managers/mcpManager.ts +6 -6
- package/src/managers/messageManager.ts +69 -10
- package/src/managers/skillManager.ts +4 -4
- package/src/managers/slashCommandManager.ts +6 -2
- package/src/managers/subagentManager.ts +58 -53
- package/src/managers/toolManager.ts +1 -1
- package/src/services/aiService.ts +37 -7
- package/src/services/hook.ts +360 -0
- package/src/services/memory.ts +3 -3
- package/src/services/session.ts +99 -33
- package/src/tools/bashTool.ts +2 -2
- package/src/tools/deleteFileTool.ts +1 -1
- package/src/tools/editTool.ts +1 -1
- package/src/tools/multiEditTool.ts +2 -2
- package/src/tools/taskTool.ts +13 -5
- package/src/tools/writeTool.ts +1 -1
- package/src/types/commands.ts +26 -0
- package/src/types/config.ts +14 -0
- package/src/types/core.ts +49 -0
- package/src/{hooks/types.ts → types/hooks.ts} +1 -0
- package/src/types/index.ts +23 -0
- package/src/{types.ts → types/index.ts.backup} +13 -0
- package/src/types/mcp.ts +31 -0
- package/src/types/messaging.ts +103 -0
- package/src/types/processes.ts +18 -0
- package/src/types/skills.ts +91 -0
- package/src/utils/configResolver.ts +1 -1
- package/src/utils/configValidator.ts +5 -1
- package/src/utils/convertMessagesForAPI.ts +1 -1
- package/src/utils/customCommands.ts +1 -1
- package/src/utils/markdownParser.ts +1 -1
- package/src/utils/mcpUtils.ts +1 -1
- package/src/utils/messageOperations.ts +22 -1
- package/src/utils/skillParser.ts +1 -1
- package/dist/hooks/executor.d.ts +0 -56
- package/dist/hooks/executor.d.ts.map +0 -1
- package/dist/hooks/executor.js +0 -312
- package/dist/hooks/index.d.ts +0 -17
- package/dist/hooks/index.d.ts.map +0 -1
- package/dist/hooks/index.js +0 -14
- package/dist/hooks/manager.d.ts.map +0 -1
- package/dist/hooks/matcher.d.ts.map +0 -1
- package/dist/hooks/settings.d.ts +0 -46
- package/dist/hooks/settings.d.ts.map +0 -1
- package/dist/hooks/settings.js +0 -100
- package/dist/hooks/types.d.ts.map +0 -1
- package/dist/types.d.ts +0 -276
- package/dist/types.d.ts.map +0 -1
- package/src/hooks/executor.ts +0 -440
- package/src/hooks/index.ts +0 -52
- package/src/hooks/settings.ts +0 -129
- /package/dist/{hooks/types.js → types/hooks.js} +0 -0
- /package/dist/{hooks/matcher.js → utils/hookMatcher.js} +0 -0
- /package/src/{hooks/matcher.ts → utils/hookMatcher.ts} +0 -0
|
@@ -2,17 +2,23 @@ import { callAgent, compressMessages } from "../services/aiService.js";
|
|
|
2
2
|
import { getMessagesToCompress } from "../utils/messageOperations.js";
|
|
3
3
|
import { convertMessagesForAPI } from "../utils/convertMessagesForAPI.js";
|
|
4
4
|
import * as memory from "../services/memory.js";
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
Logger,
|
|
7
|
+
GatewayConfig,
|
|
8
|
+
ModelConfig,
|
|
9
|
+
Usage,
|
|
10
|
+
} from "../types/index.js";
|
|
6
11
|
import type { ToolManager } from "./toolManager.js";
|
|
7
12
|
import type { ToolContext, ToolResult } from "../tools/types.js";
|
|
8
13
|
import type { MessageManager } from "./messageManager.js";
|
|
9
14
|
import type { BackgroundBashManager } from "./backgroundBashManager.js";
|
|
10
15
|
import { ChatCompletionMessageFunctionToolCall } from "openai/resources.js";
|
|
11
|
-
import type { HookManager } from "
|
|
12
|
-
import type { ExtendedHookExecutionContext } from "../hooks
|
|
16
|
+
import type { HookManager } from "./hookManager.js";
|
|
17
|
+
import type { ExtendedHookExecutionContext } from "../types/hooks.js";
|
|
13
18
|
|
|
14
19
|
export interface AIManagerCallbacks {
|
|
15
20
|
onCompressionStateChange?: (isCompressing: boolean) => void;
|
|
21
|
+
onUsageAdded?: (usage: Usage) => void;
|
|
16
22
|
}
|
|
17
23
|
|
|
18
24
|
export interface AIManagerOptions {
|
|
@@ -140,7 +146,7 @@ export class AIManager {
|
|
|
140
146
|
|
|
141
147
|
// Check if token limit exceeded - use injected configuration
|
|
142
148
|
if (usage.total_tokens > this.tokenLimit) {
|
|
143
|
-
this.logger?.
|
|
149
|
+
this.logger?.debug(
|
|
144
150
|
`Token usage exceeded ${this.tokenLimit}, compressing messages...`,
|
|
145
151
|
);
|
|
146
152
|
|
|
@@ -156,7 +162,7 @@ export class AIManager {
|
|
|
156
162
|
|
|
157
163
|
this.setIsCompressing(true);
|
|
158
164
|
try {
|
|
159
|
-
const
|
|
165
|
+
const compressionResult = await compressMessages({
|
|
160
166
|
gatewayConfig: this.gatewayConfig,
|
|
161
167
|
modelConfig: this.modelConfig,
|
|
162
168
|
messages: recentChatMessages,
|
|
@@ -166,10 +172,26 @@ export class AIManager {
|
|
|
166
172
|
// Execute message reconstruction and sessionId update after compression
|
|
167
173
|
this.messageManager.compressMessagesAndUpdateSession(
|
|
168
174
|
insertIndex,
|
|
169
|
-
|
|
175
|
+
compressionResult.content,
|
|
170
176
|
);
|
|
171
177
|
|
|
172
|
-
|
|
178
|
+
// Handle usage tracking for compression operations
|
|
179
|
+
if (compressionResult.usage) {
|
|
180
|
+
const usage: Usage = {
|
|
181
|
+
prompt_tokens: compressionResult.usage.prompt_tokens,
|
|
182
|
+
completion_tokens: compressionResult.usage.completion_tokens,
|
|
183
|
+
total_tokens: compressionResult.usage.total_tokens,
|
|
184
|
+
model: this.modelConfig.fastModel,
|
|
185
|
+
operation_type: "compress",
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// Notify Agent to add to usage tracking
|
|
189
|
+
if (this.callbacks?.onUsageAdded) {
|
|
190
|
+
this.callbacks.onUsageAdded(usage);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
this.logger?.debug(
|
|
173
195
|
`Successfully compressed ${messagesToCompress.length} messages and updated session`,
|
|
174
196
|
);
|
|
175
197
|
} catch (compressError) {
|
|
@@ -200,18 +222,29 @@ export class AIManager {
|
|
|
200
222
|
} = {},
|
|
201
223
|
): Promise<void> {
|
|
202
224
|
const { recursionDepth = 0, model, allowedTools } = options;
|
|
225
|
+
|
|
203
226
|
// Only check isLoading for the initial call (recursionDepth === 0)
|
|
204
227
|
if (recursionDepth === 0 && this.isLoading) {
|
|
205
228
|
return;
|
|
206
229
|
}
|
|
207
230
|
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
231
|
+
// Only create new AbortControllers for the initial call (recursionDepth === 0)
|
|
232
|
+
// For recursive calls, reuse existing controllers to maintain abort signal
|
|
233
|
+
let abortController: AbortController;
|
|
234
|
+
let toolAbortController: AbortController;
|
|
211
235
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
236
|
+
if (recursionDepth === 0) {
|
|
237
|
+
// Create new AbortControllers for initial call
|
|
238
|
+
abortController = new AbortController();
|
|
239
|
+
this.abortController = abortController;
|
|
240
|
+
|
|
241
|
+
toolAbortController = new AbortController();
|
|
242
|
+
this.toolAbortController = toolAbortController;
|
|
243
|
+
} else {
|
|
244
|
+
// Reuse existing controllers for recursive calls
|
|
245
|
+
abortController = this.abortController!;
|
|
246
|
+
toolAbortController = this.toolAbortController!;
|
|
247
|
+
}
|
|
215
248
|
|
|
216
249
|
// Only set loading state for the initial call
|
|
217
250
|
if (recursionDepth === 0) {
|
|
@@ -255,143 +288,171 @@ export class AIManager {
|
|
|
255
288
|
}
|
|
256
289
|
}
|
|
257
290
|
|
|
258
|
-
//
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
toolAbortController.signal.aborted
|
|
270
|
-
) {
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Safely parse tool parameters, handle tools without parameters
|
|
275
|
-
let toolArgs: Record<string, unknown> = {};
|
|
276
|
-
const argsString = functionToolCall.function?.arguments?.trim();
|
|
291
|
+
// Handle usage tracking for agent operations
|
|
292
|
+
let usage: Usage | undefined;
|
|
293
|
+
if (result.usage) {
|
|
294
|
+
usage = {
|
|
295
|
+
prompt_tokens: result.usage.prompt_tokens,
|
|
296
|
+
completion_tokens: result.usage.completion_tokens,
|
|
297
|
+
total_tokens: result.usage.total_tokens,
|
|
298
|
+
model: model || this.modelConfig.agentModel,
|
|
299
|
+
operation_type: "agent",
|
|
300
|
+
};
|
|
301
|
+
}
|
|
277
302
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
toolArgs = {};
|
|
281
|
-
} else {
|
|
282
|
-
try {
|
|
283
|
-
toolArgs = JSON.parse(argsString);
|
|
284
|
-
} catch (parseError) {
|
|
285
|
-
// For non-empty but malformed JSON, still throw exception
|
|
286
|
-
const errorMessage = `Failed to parse tool arguments: ${argsString}`;
|
|
287
|
-
this.logger?.error(errorMessage, parseError);
|
|
288
|
-
throw new Error(errorMessage);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
303
|
+
// Add assistant message at once (including content, tool calls, and usage)
|
|
304
|
+
this.messageManager.addAssistantMessage(content, toolCalls, usage);
|
|
291
305
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
306
|
+
// Notify Agent to add to usage tracking
|
|
307
|
+
if (usage) {
|
|
308
|
+
if (this.callbacks?.onUsageAdded) {
|
|
309
|
+
this.callbacks.onUsageAdded(usage);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
298
312
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
compactParams,
|
|
305
|
-
});
|
|
313
|
+
if (toolCalls.length > 0) {
|
|
314
|
+
// Execute all tools in parallel using Promise.all
|
|
315
|
+
const toolExecutionPromises = toolCalls.map(
|
|
316
|
+
async (functionToolCall) => {
|
|
317
|
+
const toolId = functionToolCall.id || "";
|
|
306
318
|
|
|
307
319
|
try {
|
|
308
|
-
//
|
|
309
|
-
await this.executePreToolUseHooks(toolName, toolArgs);
|
|
310
|
-
|
|
311
|
-
// Create tool execution context
|
|
312
|
-
const context: ToolContext = {
|
|
313
|
-
abortSignal: toolAbortController.signal,
|
|
314
|
-
backgroundBashManager: this.backgroundBashManager,
|
|
315
|
-
workdir: this.workdir,
|
|
316
|
-
};
|
|
317
|
-
|
|
318
|
-
// Execute tool
|
|
319
|
-
const toolResult = await this.toolManager.execute(
|
|
320
|
-
functionToolCall.function?.name || "",
|
|
321
|
-
toolArgs,
|
|
322
|
-
context,
|
|
323
|
-
);
|
|
324
|
-
|
|
325
|
-
// Update message state - tool execution completed
|
|
326
|
-
this.messageManager.updateToolBlock({
|
|
327
|
-
toolId,
|
|
328
|
-
args: JSON.stringify(toolArgs, null, 2),
|
|
329
|
-
result:
|
|
330
|
-
toolResult.content ||
|
|
331
|
-
(toolResult.error ? `Error: ${toolResult.error}` : ""),
|
|
332
|
-
success: toolResult.success,
|
|
333
|
-
error: toolResult.error,
|
|
334
|
-
isRunning: false, // isRunning: false
|
|
335
|
-
name: toolName,
|
|
336
|
-
shortResult: toolResult.shortResult,
|
|
337
|
-
compactParams,
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
// If tool returns diff information, add diff block
|
|
320
|
+
// Check if already interrupted, skip tool execution if so
|
|
341
321
|
if (
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
toolResult.filePath
|
|
322
|
+
abortController.signal.aborted ||
|
|
323
|
+
toolAbortController.signal.aborted
|
|
345
324
|
) {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Safely parse tool parameters, handle tools without parameters
|
|
329
|
+
let toolArgs: Record<string, unknown> = {};
|
|
330
|
+
const argsString = functionToolCall.function?.arguments?.trim();
|
|
331
|
+
|
|
332
|
+
if (!argsString || argsString === "") {
|
|
333
|
+
// Tool without parameters, use empty object
|
|
334
|
+
toolArgs = {};
|
|
335
|
+
} else {
|
|
336
|
+
try {
|
|
337
|
+
toolArgs = JSON.parse(argsString);
|
|
338
|
+
} catch (parseError) {
|
|
339
|
+
// For non-empty but malformed JSON, still throw exception
|
|
340
|
+
const errorMessage = `Failed to parse tool arguments: ${argsString}`;
|
|
341
|
+
this.logger?.error(errorMessage, parseError);
|
|
342
|
+
throw new Error(errorMessage);
|
|
343
|
+
}
|
|
350
344
|
}
|
|
351
345
|
|
|
352
|
-
//
|
|
353
|
-
|
|
346
|
+
// Set tool start execution state
|
|
347
|
+
const toolName = functionToolCall.function?.name || "";
|
|
348
|
+
const compactParams = this.generateCompactParams(
|
|
354
349
|
toolName,
|
|
355
350
|
toolArgs,
|
|
356
|
-
toolResult,
|
|
357
351
|
);
|
|
358
|
-
} catch (toolError) {
|
|
359
|
-
const errorMessage =
|
|
360
|
-
toolError instanceof Error
|
|
361
|
-
? toolError.message
|
|
362
|
-
: String(toolError);
|
|
363
352
|
|
|
364
353
|
this.messageManager.updateToolBlock({
|
|
365
354
|
toolId,
|
|
366
355
|
args: JSON.stringify(toolArgs, null, 2),
|
|
367
|
-
|
|
368
|
-
success: false,
|
|
369
|
-
error: errorMessage,
|
|
370
|
-
isRunning: false,
|
|
356
|
+
isRunning: true, // isRunning: true
|
|
371
357
|
name: toolName,
|
|
372
358
|
compactParams,
|
|
373
359
|
});
|
|
360
|
+
|
|
361
|
+
try {
|
|
362
|
+
// Execute PreToolUse hooks before tool execution
|
|
363
|
+
const shouldExecuteTool = await this.executePreToolUseHooks(
|
|
364
|
+
toolName,
|
|
365
|
+
toolArgs,
|
|
366
|
+
toolId,
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
// If PreToolUse hooks blocked execution, skip tool execution
|
|
370
|
+
if (!shouldExecuteTool) {
|
|
371
|
+
this.logger?.info(
|
|
372
|
+
`Tool ${toolName} execution blocked by PreToolUse hooks`,
|
|
373
|
+
);
|
|
374
|
+
return; // Skip this tool and return from this map function
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Create tool execution context
|
|
378
|
+
const context: ToolContext = {
|
|
379
|
+
abortSignal: toolAbortController.signal,
|
|
380
|
+
backgroundBashManager: this.backgroundBashManager,
|
|
381
|
+
workdir: this.workdir,
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
// Execute tool
|
|
385
|
+
const toolResult = await this.toolManager.execute(
|
|
386
|
+
functionToolCall.function?.name || "",
|
|
387
|
+
toolArgs,
|
|
388
|
+
context,
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
// Update message state - tool execution completed
|
|
392
|
+
this.messageManager.updateToolBlock({
|
|
393
|
+
toolId,
|
|
394
|
+
args: JSON.stringify(toolArgs, null, 2),
|
|
395
|
+
result:
|
|
396
|
+
toolResult.content ||
|
|
397
|
+
(toolResult.error ? `Error: ${toolResult.error}` : ""),
|
|
398
|
+
success: toolResult.success,
|
|
399
|
+
error: toolResult.error,
|
|
400
|
+
isRunning: false, // isRunning: false
|
|
401
|
+
name: toolName,
|
|
402
|
+
shortResult: toolResult.shortResult,
|
|
403
|
+
compactParams,
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// If tool returns diff information, add diff block
|
|
407
|
+
if (
|
|
408
|
+
toolResult.success &&
|
|
409
|
+
toolResult.diffResult &&
|
|
410
|
+
toolResult.filePath
|
|
411
|
+
) {
|
|
412
|
+
this.messageManager.addDiffBlock(
|
|
413
|
+
toolResult.filePath,
|
|
414
|
+
toolResult.diffResult,
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Execute PostToolUse hooks after successful tool completion
|
|
419
|
+
await this.executePostToolUseHooks(
|
|
420
|
+
toolId,
|
|
421
|
+
toolName,
|
|
422
|
+
toolArgs,
|
|
423
|
+
toolResult,
|
|
424
|
+
);
|
|
425
|
+
} catch (toolError) {
|
|
426
|
+
const errorMessage =
|
|
427
|
+
toolError instanceof Error
|
|
428
|
+
? toolError.message
|
|
429
|
+
: String(toolError);
|
|
430
|
+
|
|
431
|
+
this.messageManager.updateToolBlock({
|
|
432
|
+
toolId,
|
|
433
|
+
args: JSON.stringify(toolArgs, null, 2),
|
|
434
|
+
result: `Tool execution failed: ${errorMessage}`,
|
|
435
|
+
success: false,
|
|
436
|
+
error: errorMessage,
|
|
437
|
+
isRunning: false,
|
|
438
|
+
name: toolName,
|
|
439
|
+
compactParams,
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
} catch (parseError) {
|
|
443
|
+
const errorMessage =
|
|
444
|
+
parseError instanceof Error
|
|
445
|
+
? parseError.message
|
|
446
|
+
: String(parseError);
|
|
447
|
+
this.messageManager.addErrorBlock(
|
|
448
|
+
`Failed to parse tool arguments for ${functionToolCall.function?.name}: ${errorMessage}`,
|
|
449
|
+
);
|
|
374
450
|
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
const isAborted =
|
|
378
|
-
abortController.signal.aborted ||
|
|
379
|
-
toolAbortController.signal.aborted;
|
|
380
|
-
|
|
381
|
-
if (isAborted) {
|
|
382
|
-
// If interrupted, return directly without showing error
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
451
|
+
},
|
|
452
|
+
);
|
|
385
453
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
? parseError.message
|
|
389
|
-
: String(parseError);
|
|
390
|
-
this.messageManager.addErrorBlock(
|
|
391
|
-
`Failed to parse tool arguments for ${functionToolCall.function?.name}: ${errorMessage}`,
|
|
392
|
-
);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
454
|
+
// Wait for all tools to complete execution in parallel
|
|
455
|
+
await Promise.all(toolExecutionPromises);
|
|
395
456
|
}
|
|
396
457
|
|
|
397
458
|
// Handle token statistics and message compression
|
|
@@ -403,12 +464,6 @@ export class AIManager {
|
|
|
403
464
|
const isCurrentlyAborted =
|
|
404
465
|
abortController.signal.aborted || toolAbortController.signal.aborted;
|
|
405
466
|
|
|
406
|
-
// AI service call ends, clear abort controller
|
|
407
|
-
this.abortController = null;
|
|
408
|
-
|
|
409
|
-
// Clear tool AbortController after tool execution completes
|
|
410
|
-
this.toolAbortController = null;
|
|
411
|
-
|
|
412
467
|
if (!isCurrentlyAborted) {
|
|
413
468
|
// Recursively call AI service, increment recursion depth, and pass same configuration
|
|
414
469
|
await this.sendAIMessage({
|
|
@@ -417,47 +472,57 @@ export class AIManager {
|
|
|
417
472
|
allowedTools,
|
|
418
473
|
});
|
|
419
474
|
}
|
|
420
|
-
} else {
|
|
421
|
-
// Clear abort controller when no tool operations
|
|
422
|
-
this.abortController = null;
|
|
423
|
-
this.toolAbortController = null;
|
|
424
475
|
}
|
|
425
476
|
} catch (error) {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
toolAbortController.signal.aborted ||
|
|
430
|
-
(error instanceof Error &&
|
|
431
|
-
(error.name === "AbortError" || error.message.includes("aborted")));
|
|
432
|
-
|
|
433
|
-
if (!isAborted) {
|
|
434
|
-
this.messageManager.addErrorBlock(
|
|
435
|
-
error instanceof Error ? error.message : "Unknown error occurred",
|
|
436
|
-
);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// Reset abort controller on error
|
|
440
|
-
this.abortController = null;
|
|
441
|
-
this.toolAbortController = null;
|
|
477
|
+
this.messageManager.addErrorBlock(
|
|
478
|
+
error instanceof Error ? error.message : "Unknown error occurred",
|
|
479
|
+
);
|
|
442
480
|
} finally {
|
|
443
|
-
// Only
|
|
481
|
+
// Only execute Stop hooks for the initial call
|
|
444
482
|
if (recursionDepth === 0) {
|
|
445
|
-
|
|
483
|
+
// Execute Stop hooks only if the operation was not aborted
|
|
484
|
+
const isCurrentlyAborted =
|
|
485
|
+
abortController.signal.aborted || toolAbortController.signal.aborted;
|
|
486
|
+
|
|
487
|
+
if (!isCurrentlyAborted) {
|
|
488
|
+
const shouldContinue = await this.executeStopHooks();
|
|
489
|
+
|
|
490
|
+
// If Stop hooks indicate we should continue (due to blocking errors),
|
|
491
|
+
// restart the AI conversation cycle
|
|
492
|
+
if (shouldContinue) {
|
|
493
|
+
this.logger?.info(
|
|
494
|
+
"Stop hooks indicate issues need fixing, continuing conversation...",
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
// Restart the conversation to let AI fix the issues
|
|
498
|
+
// Use recursionDepth = 1 to prevent Stop hooks from running again in continuation
|
|
499
|
+
await this.sendAIMessage({
|
|
500
|
+
recursionDepth: 1,
|
|
501
|
+
model,
|
|
502
|
+
allowedTools,
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
}
|
|
446
506
|
|
|
447
|
-
// Save session
|
|
507
|
+
// Save session after all operations (including continuation) are complete
|
|
448
508
|
await this.messageManager.saveSession();
|
|
449
509
|
|
|
450
|
-
//
|
|
451
|
-
|
|
510
|
+
// Clear abort controllers and loading state after all operations are complete
|
|
511
|
+
this.abortController = null;
|
|
512
|
+
this.toolAbortController = null;
|
|
513
|
+
|
|
514
|
+
// Set loading to false at the very end, after all operations including continuation
|
|
515
|
+
this.setIsLoading(false);
|
|
452
516
|
}
|
|
453
517
|
}
|
|
454
518
|
}
|
|
455
519
|
|
|
456
520
|
/**
|
|
457
521
|
* Execute Stop hooks when AI response cycle completes
|
|
522
|
+
* @returns Promise<boolean> - true if should continue conversation, false if should stop
|
|
458
523
|
*/
|
|
459
|
-
private async executeStopHooks(): Promise<
|
|
460
|
-
if (!this.hookManager) return;
|
|
524
|
+
private async executeStopHooks(): Promise<boolean> {
|
|
525
|
+
if (!this.hookManager) return false;
|
|
461
526
|
|
|
462
527
|
try {
|
|
463
528
|
const context: ExtendedHookExecutionContext = {
|
|
@@ -472,6 +537,25 @@ export class AIManager {
|
|
|
472
537
|
|
|
473
538
|
const results = await this.hookManager.executeHooks("Stop", context);
|
|
474
539
|
|
|
540
|
+
// Process hook results to handle exit codes and appropriate responses
|
|
541
|
+
let shouldContinue = false;
|
|
542
|
+
if (results.length > 0) {
|
|
543
|
+
const processResult = this.hookManager.processHookResults(
|
|
544
|
+
"Stop",
|
|
545
|
+
results,
|
|
546
|
+
this.messageManager,
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
// If hook processing indicates we should block (exit code 2), continue conversation
|
|
550
|
+
if (processResult.shouldBlock) {
|
|
551
|
+
this.logger?.info(
|
|
552
|
+
"Stop hook blocked stopping with error:",
|
|
553
|
+
processResult.errorMessage,
|
|
554
|
+
);
|
|
555
|
+
shouldContinue = true;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
475
559
|
// Log hook execution results for debugging
|
|
476
560
|
if (results.length > 0) {
|
|
477
561
|
this.logger?.debug(
|
|
@@ -485,20 +569,25 @@ export class AIManager {
|
|
|
485
569
|
})),
|
|
486
570
|
);
|
|
487
571
|
}
|
|
572
|
+
|
|
573
|
+
return shouldContinue;
|
|
488
574
|
} catch (error) {
|
|
489
575
|
// Hook execution errors should not interrupt the main workflow
|
|
490
576
|
this.logger?.error("Stop hook execution failed:", error);
|
|
577
|
+
return false;
|
|
491
578
|
}
|
|
492
579
|
}
|
|
493
580
|
|
|
494
581
|
/**
|
|
495
582
|
* Execute PreToolUse hooks before tool execution
|
|
583
|
+
* Returns true if hooks allow tool execution, false if blocked
|
|
496
584
|
*/
|
|
497
585
|
private async executePreToolUseHooks(
|
|
498
586
|
toolName: string,
|
|
499
587
|
toolInput?: Record<string, unknown>,
|
|
500
|
-
|
|
501
|
-
|
|
588
|
+
toolId?: string,
|
|
589
|
+
): Promise<boolean> {
|
|
590
|
+
if (!this.hookManager) return true;
|
|
502
591
|
|
|
503
592
|
try {
|
|
504
593
|
const context: ExtendedHookExecutionContext = {
|
|
@@ -517,6 +606,18 @@ export class AIManager {
|
|
|
517
606
|
context,
|
|
518
607
|
);
|
|
519
608
|
|
|
609
|
+
// Process hook results to handle exit codes and determine if tool should be blocked
|
|
610
|
+
let shouldContinue = true;
|
|
611
|
+
if (results.length > 0) {
|
|
612
|
+
const processResult = this.hookManager.processHookResults(
|
|
613
|
+
"PreToolUse",
|
|
614
|
+
results,
|
|
615
|
+
this.messageManager,
|
|
616
|
+
toolId, // Pass toolId for proper PreToolUse blocking error handling
|
|
617
|
+
);
|
|
618
|
+
shouldContinue = !processResult.shouldBlock;
|
|
619
|
+
}
|
|
620
|
+
|
|
520
621
|
// Log hook execution results for debugging
|
|
521
622
|
if (results.length > 0) {
|
|
522
623
|
this.logger?.debug(
|
|
@@ -530,9 +631,12 @@ export class AIManager {
|
|
|
530
631
|
})),
|
|
531
632
|
);
|
|
532
633
|
}
|
|
634
|
+
|
|
635
|
+
return shouldContinue;
|
|
533
636
|
} catch (error) {
|
|
534
637
|
// Hook execution errors should not interrupt the main workflow
|
|
535
638
|
this.logger?.error("PreToolUse hook execution failed:", error);
|
|
639
|
+
return true; // Allow tool execution on hook errors
|
|
536
640
|
}
|
|
537
641
|
}
|
|
538
642
|
|
|
@@ -540,6 +644,7 @@ export class AIManager {
|
|
|
540
644
|
* Execute PostToolUse hooks after tool completion
|
|
541
645
|
*/
|
|
542
646
|
private async executePostToolUseHooks(
|
|
647
|
+
toolId: string,
|
|
543
648
|
toolName: string,
|
|
544
649
|
toolInput?: Record<string, unknown>,
|
|
545
650
|
toolResponse?: ToolResult,
|
|
@@ -564,6 +669,19 @@ export class AIManager {
|
|
|
564
669
|
context,
|
|
565
670
|
);
|
|
566
671
|
|
|
672
|
+
// Process hook results to handle exit codes and update tool results
|
|
673
|
+
if (results.length > 0) {
|
|
674
|
+
const originalToolResult = toolResponse?.content || "";
|
|
675
|
+
|
|
676
|
+
this.hookManager.processHookResults(
|
|
677
|
+
"PostToolUse",
|
|
678
|
+
results,
|
|
679
|
+
this.messageManager,
|
|
680
|
+
toolId,
|
|
681
|
+
originalToolResult,
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
|
|
567
685
|
// Log hook execution results for debugging
|
|
568
686
|
if (results.length > 0) {
|
|
569
687
|
this.logger?.debug(
|