wave-agent-sdk 0.0.5 → 0.0.7
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 +3 -6
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +24 -21
- 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 +4 -2
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +91 -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} +27 -16
- package/dist/managers/hookManager.d.ts.map +1 -0
- package/dist/{hooks/manager.js → managers/hookManager.js} +112 -17
- package/dist/managers/mcpManager.d.ts +1 -1
- package/dist/managers/mcpManager.d.ts.map +1 -1
- package/dist/managers/messageManager.d.ts +20 -15
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +19 -25
- package/dist/managers/skillManager.d.ts +1 -1
- package/dist/managers/skillManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.d.ts +1 -1
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +5 -2
- package/dist/managers/subagentManager.d.ts +7 -12
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +40 -46
- package/dist/managers/toolManager.d.ts +1 -1
- package/dist/managers/toolManager.d.ts.map +1 -1
- package/dist/services/aiService.d.ts +1 -1
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +8 -1
- 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/session.d.ts +1 -1
- package/dist/services/session.d.ts.map +1 -1
- package/dist/services/session.js +5 -4
- package/dist/tools/taskTool.d.ts.map +1 -1
- package/dist/tools/taskTool.js +7 -3
- package/dist/tools/todoWriteTool.d.ts.map +1 -1
- package/dist/tools/todoWriteTool.js +3 -10
- 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 +9 -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/convertMessagesForAPI.js +1 -8
- 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} +2 -7
- 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 +14 -21
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +37 -20
- 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 +49 -43
- package/src/index.ts +3 -4
- package/src/managers/aiManager.ts +241 -160
- package/src/managers/backgroundBashManager.ts +1 -1
- package/src/{hooks/manager.ts → managers/hookManager.ts} +168 -56
- package/src/managers/mcpManager.ts +1 -1
- package/src/managers/messageManager.ts +44 -44
- package/src/managers/skillManager.ts +1 -1
- package/src/managers/slashCommandManager.ts +10 -7
- package/src/managers/subagentManager.ts +47 -54
- package/src/managers/toolManager.ts +1 -1
- package/src/services/aiService.ts +9 -2
- package/src/services/hook.ts +360 -0
- package/src/services/session.ts +6 -7
- package/src/tools/taskTool.ts +13 -5
- package/src/tools/todoWriteTool.ts +3 -11
- 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/mcp.ts +31 -0
- package/src/types/messaging.ts +102 -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 +2 -10
- package/src/utils/customCommands.ts +1 -1
- package/src/{hooks/matcher.ts → utils/hookMatcher.ts} +1 -12
- package/src/utils/markdownParser.ts +1 -1
- package/src/utils/mcpUtils.ts +1 -1
- package/src/utils/messageOperations.ts +56 -42
- 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 -288
- 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/src/types.ts +0 -357
- /package/dist/{hooks/types.js → types/hooks.js} +0 -0
- /package/dist/{hooks/matcher.js → utils/hookMatcher.js} +0 -0
|
@@ -2,14 +2,19 @@ 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;
|
|
@@ -217,18 +222,29 @@ export class AIManager {
|
|
|
217
222
|
} = {},
|
|
218
223
|
): Promise<void> {
|
|
219
224
|
const { recursionDepth = 0, model, allowedTools } = options;
|
|
225
|
+
|
|
220
226
|
// Only check isLoading for the initial call (recursionDepth === 0)
|
|
221
227
|
if (recursionDepth === 0 && this.isLoading) {
|
|
222
228
|
return;
|
|
223
229
|
}
|
|
224
230
|
|
|
225
|
-
//
|
|
226
|
-
|
|
227
|
-
|
|
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;
|
|
228
235
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
+
}
|
|
232
248
|
|
|
233
249
|
// Only set loading state for the initial call
|
|
234
250
|
if (recursionDepth === 0) {
|
|
@@ -266,6 +282,7 @@ export class AIManager {
|
|
|
266
282
|
|
|
267
283
|
if (result.tool_calls) {
|
|
268
284
|
for (const toolCall of result.tool_calls) {
|
|
285
|
+
this.logger?.debug("ToolCall", toolCall);
|
|
269
286
|
if (toolCall.type === "function") {
|
|
270
287
|
toolCalls.push(toolCall);
|
|
271
288
|
}
|
|
@@ -295,139 +312,148 @@ export class AIManager {
|
|
|
295
312
|
}
|
|
296
313
|
|
|
297
314
|
if (toolCalls.length > 0) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
// Check if already interrupted, skip tool execution if so
|
|
303
|
-
if (
|
|
304
|
-
abortController.signal.aborted ||
|
|
305
|
-
toolAbortController.signal.aborted
|
|
306
|
-
) {
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Safely parse tool parameters, handle tools without parameters
|
|
311
|
-
let toolArgs: Record<string, unknown> = {};
|
|
312
|
-
const argsString = functionToolCall.function?.arguments?.trim();
|
|
315
|
+
// Execute all tools in parallel using Promise.all
|
|
316
|
+
const toolExecutionPromises = toolCalls.map(
|
|
317
|
+
async (functionToolCall) => {
|
|
318
|
+
const toolId = functionToolCall.id || "";
|
|
313
319
|
|
|
314
|
-
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
// For non-empty but malformed JSON, still throw exception
|
|
322
|
-
const errorMessage = `Failed to parse tool arguments: ${argsString}`;
|
|
323
|
-
this.logger?.error(errorMessage, parseError);
|
|
324
|
-
throw new Error(errorMessage);
|
|
320
|
+
try {
|
|
321
|
+
// Check if already interrupted, skip tool execution if so
|
|
322
|
+
if (
|
|
323
|
+
abortController.signal.aborted ||
|
|
324
|
+
toolAbortController.signal.aborted
|
|
325
|
+
) {
|
|
326
|
+
return;
|
|
325
327
|
}
|
|
326
|
-
}
|
|
327
328
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
329
|
+
// Safely parse tool parameters, handle tools without parameters
|
|
330
|
+
let toolArgs: Record<string, unknown> = {};
|
|
331
|
+
const argsString = functionToolCall.function?.arguments?.trim();
|
|
332
|
+
|
|
333
|
+
if (!argsString || argsString === "") {
|
|
334
|
+
// Tool without parameters, use empty object
|
|
335
|
+
toolArgs = {};
|
|
336
|
+
} else {
|
|
337
|
+
try {
|
|
338
|
+
toolArgs = JSON.parse(argsString);
|
|
339
|
+
} catch (parseError) {
|
|
340
|
+
// For non-empty but malformed JSON, still throw exception
|
|
341
|
+
const errorMessage = `Failed to parse tool arguments: ${argsString}`;
|
|
342
|
+
this.logger?.error(errorMessage, parseError);
|
|
343
|
+
throw new Error(errorMessage);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
342
346
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
// Create tool execution context
|
|
348
|
-
const context: ToolContext = {
|
|
349
|
-
abortSignal: toolAbortController.signal,
|
|
350
|
-
backgroundBashManager: this.backgroundBashManager,
|
|
351
|
-
workdir: this.workdir,
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
// Execute tool
|
|
355
|
-
const toolResult = await this.toolManager.execute(
|
|
356
|
-
functionToolCall.function?.name || "",
|
|
347
|
+
// Set tool start execution state
|
|
348
|
+
const toolName = functionToolCall.function?.name || "";
|
|
349
|
+
const compactParams = this.generateCompactParams(
|
|
350
|
+
toolName,
|
|
357
351
|
toolArgs,
|
|
358
|
-
context,
|
|
359
352
|
);
|
|
360
353
|
|
|
361
|
-
// Update message state - tool execution completed
|
|
362
354
|
this.messageManager.updateToolBlock({
|
|
363
|
-
toolId,
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
toolResult.content ||
|
|
367
|
-
(toolResult.error ? `Error: ${toolResult.error}` : ""),
|
|
368
|
-
success: toolResult.success,
|
|
369
|
-
error: toolResult.error,
|
|
370
|
-
isRunning: false, // isRunning: false
|
|
355
|
+
id: toolId,
|
|
356
|
+
parameters: JSON.stringify(toolArgs, null, 2),
|
|
357
|
+
isRunning: true, // isRunning: true
|
|
371
358
|
name: toolName,
|
|
372
|
-
shortResult: toolResult.shortResult,
|
|
373
359
|
compactParams,
|
|
374
360
|
});
|
|
375
361
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
this.messageManager.addDiffBlock(
|
|
383
|
-
toolResult.filePath,
|
|
384
|
-
toolResult.diffResult,
|
|
362
|
+
try {
|
|
363
|
+
// Execute PreToolUse hooks before tool execution
|
|
364
|
+
const shouldExecuteTool = await this.executePreToolUseHooks(
|
|
365
|
+
toolName,
|
|
366
|
+
toolArgs,
|
|
367
|
+
toolId,
|
|
385
368
|
);
|
|
386
|
-
}
|
|
387
369
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
:
|
|
370
|
+
// If PreToolUse hooks blocked execution, skip tool execution
|
|
371
|
+
if (!shouldExecuteTool) {
|
|
372
|
+
this.logger?.info(
|
|
373
|
+
`Tool ${toolName} execution blocked by PreToolUse hooks`,
|
|
374
|
+
);
|
|
375
|
+
return; // Skip this tool and return from this map function
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Create tool execution context
|
|
379
|
+
const context: ToolContext = {
|
|
380
|
+
abortSignal: toolAbortController.signal,
|
|
381
|
+
backgroundBashManager: this.backgroundBashManager,
|
|
382
|
+
workdir: this.workdir,
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
// Execute tool
|
|
386
|
+
const toolResult = await this.toolManager.execute(
|
|
387
|
+
functionToolCall.function?.name || "",
|
|
388
|
+
toolArgs,
|
|
389
|
+
context,
|
|
390
|
+
);
|
|
399
391
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
392
|
+
// Update message state - tool execution completed
|
|
393
|
+
this.messageManager.updateToolBlock({
|
|
394
|
+
id: toolId,
|
|
395
|
+
parameters: JSON.stringify(toolArgs, null, 2),
|
|
396
|
+
result:
|
|
397
|
+
toolResult.content ||
|
|
398
|
+
(toolResult.error ? `Error: ${toolResult.error}` : ""),
|
|
399
|
+
success: toolResult.success,
|
|
400
|
+
error: toolResult.error,
|
|
401
|
+
isRunning: false, // isRunning: false
|
|
402
|
+
name: toolName,
|
|
403
|
+
shortResult: toolResult.shortResult,
|
|
404
|
+
compactParams,
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// If tool returns diff information, add diff block
|
|
408
|
+
if (
|
|
409
|
+
toolResult.success &&
|
|
410
|
+
toolResult.diffResult &&
|
|
411
|
+
toolResult.filePath
|
|
412
|
+
) {
|
|
413
|
+
this.messageManager.addDiffBlock(
|
|
414
|
+
toolResult.filePath,
|
|
415
|
+
toolResult.diffResult,
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Execute PostToolUse hooks after successful tool completion
|
|
420
|
+
await this.executePostToolUseHooks(
|
|
421
|
+
toolId,
|
|
422
|
+
toolName,
|
|
423
|
+
toolArgs,
|
|
424
|
+
toolResult,
|
|
425
|
+
);
|
|
426
|
+
} catch (toolError) {
|
|
427
|
+
const errorMessage =
|
|
428
|
+
toolError instanceof Error
|
|
429
|
+
? toolError.message
|
|
430
|
+
: String(toolError);
|
|
431
|
+
|
|
432
|
+
this.messageManager.updateToolBlock({
|
|
433
|
+
id: toolId,
|
|
434
|
+
parameters: JSON.stringify(toolArgs, null, 2),
|
|
435
|
+
result: `Tool execution failed: ${errorMessage}`,
|
|
436
|
+
success: false,
|
|
437
|
+
error: errorMessage,
|
|
438
|
+
isRunning: false,
|
|
439
|
+
name: toolName,
|
|
440
|
+
compactParams,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
} catch (parseError) {
|
|
444
|
+
const errorMessage =
|
|
445
|
+
parseError instanceof Error
|
|
446
|
+
? parseError.message
|
|
447
|
+
: String(parseError);
|
|
448
|
+
this.messageManager.addErrorBlock(
|
|
449
|
+
`Failed to parse tool arguments for ${functionToolCall.function?.name}: ${errorMessage}`,
|
|
450
|
+
);
|
|
420
451
|
}
|
|
452
|
+
},
|
|
453
|
+
);
|
|
421
454
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
? parseError.message
|
|
425
|
-
: String(parseError);
|
|
426
|
-
this.messageManager.addErrorBlock(
|
|
427
|
-
`Failed to parse tool arguments for ${functionToolCall.function?.name}: ${errorMessage}`,
|
|
428
|
-
);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
455
|
+
// Wait for all tools to complete execution in parallel
|
|
456
|
+
await Promise.all(toolExecutionPromises);
|
|
431
457
|
}
|
|
432
458
|
|
|
433
459
|
// Handle token statistics and message compression
|
|
@@ -439,12 +465,6 @@ export class AIManager {
|
|
|
439
465
|
const isCurrentlyAborted =
|
|
440
466
|
abortController.signal.aborted || toolAbortController.signal.aborted;
|
|
441
467
|
|
|
442
|
-
// AI service call ends, clear abort controller
|
|
443
|
-
this.abortController = null;
|
|
444
|
-
|
|
445
|
-
// Clear tool AbortController after tool execution completes
|
|
446
|
-
this.toolAbortController = null;
|
|
447
|
-
|
|
448
468
|
if (!isCurrentlyAborted) {
|
|
449
469
|
// Recursively call AI service, increment recursion depth, and pass same configuration
|
|
450
470
|
await this.sendAIMessage({
|
|
@@ -453,47 +473,57 @@ export class AIManager {
|
|
|
453
473
|
allowedTools,
|
|
454
474
|
});
|
|
455
475
|
}
|
|
456
|
-
} else {
|
|
457
|
-
// Clear abort controller when no tool operations
|
|
458
|
-
this.abortController = null;
|
|
459
|
-
this.toolAbortController = null;
|
|
460
476
|
}
|
|
461
477
|
} catch (error) {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
toolAbortController.signal.aborted ||
|
|
466
|
-
(error instanceof Error &&
|
|
467
|
-
(error.name === "AbortError" || error.message.includes("aborted")));
|
|
468
|
-
|
|
469
|
-
if (!isAborted) {
|
|
470
|
-
this.messageManager.addErrorBlock(
|
|
471
|
-
error instanceof Error ? error.message : "Unknown error occurred",
|
|
472
|
-
);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// Reset abort controller on error
|
|
476
|
-
this.abortController = null;
|
|
477
|
-
this.toolAbortController = null;
|
|
478
|
+
this.messageManager.addErrorBlock(
|
|
479
|
+
error instanceof Error ? error.message : "Unknown error occurred",
|
|
480
|
+
);
|
|
478
481
|
} finally {
|
|
479
|
-
// Only
|
|
482
|
+
// Only execute Stop hooks for the initial call
|
|
480
483
|
if (recursionDepth === 0) {
|
|
481
|
-
|
|
484
|
+
// Execute Stop hooks only if the operation was not aborted
|
|
485
|
+
const isCurrentlyAborted =
|
|
486
|
+
abortController.signal.aborted || toolAbortController.signal.aborted;
|
|
487
|
+
|
|
488
|
+
if (!isCurrentlyAborted) {
|
|
489
|
+
const shouldContinue = await this.executeStopHooks();
|
|
482
490
|
|
|
483
|
-
|
|
491
|
+
// If Stop hooks indicate we should continue (due to blocking errors),
|
|
492
|
+
// restart the AI conversation cycle
|
|
493
|
+
if (shouldContinue) {
|
|
494
|
+
this.logger?.info(
|
|
495
|
+
"Stop hooks indicate issues need fixing, continuing conversation...",
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
// Restart the conversation to let AI fix the issues
|
|
499
|
+
// Use recursionDepth = 1 to prevent Stop hooks from running again in continuation
|
|
500
|
+
await this.sendAIMessage({
|
|
501
|
+
recursionDepth: 1,
|
|
502
|
+
model,
|
|
503
|
+
allowedTools,
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Save session after all operations (including continuation) are complete
|
|
484
509
|
await this.messageManager.saveSession();
|
|
485
510
|
|
|
486
|
-
//
|
|
487
|
-
|
|
511
|
+
// Clear abort controllers and loading state after all operations are complete
|
|
512
|
+
this.abortController = null;
|
|
513
|
+
this.toolAbortController = null;
|
|
514
|
+
|
|
515
|
+
// Set loading to false at the very end, after all operations including continuation
|
|
516
|
+
this.setIsLoading(false);
|
|
488
517
|
}
|
|
489
518
|
}
|
|
490
519
|
}
|
|
491
520
|
|
|
492
521
|
/**
|
|
493
522
|
* Execute Stop hooks when AI response cycle completes
|
|
523
|
+
* @returns Promise<boolean> - true if should continue conversation, false if should stop
|
|
494
524
|
*/
|
|
495
|
-
private async executeStopHooks(): Promise<
|
|
496
|
-
if (!this.hookManager) return;
|
|
525
|
+
private async executeStopHooks(): Promise<boolean> {
|
|
526
|
+
if (!this.hookManager) return false;
|
|
497
527
|
|
|
498
528
|
try {
|
|
499
529
|
const context: ExtendedHookExecutionContext = {
|
|
@@ -508,6 +538,25 @@ export class AIManager {
|
|
|
508
538
|
|
|
509
539
|
const results = await this.hookManager.executeHooks("Stop", context);
|
|
510
540
|
|
|
541
|
+
// Process hook results to handle exit codes and appropriate responses
|
|
542
|
+
let shouldContinue = false;
|
|
543
|
+
if (results.length > 0) {
|
|
544
|
+
const processResult = this.hookManager.processHookResults(
|
|
545
|
+
"Stop",
|
|
546
|
+
results,
|
|
547
|
+
this.messageManager,
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
// If hook processing indicates we should block (exit code 2), continue conversation
|
|
551
|
+
if (processResult.shouldBlock) {
|
|
552
|
+
this.logger?.info(
|
|
553
|
+
"Stop hook blocked stopping with error:",
|
|
554
|
+
processResult.errorMessage,
|
|
555
|
+
);
|
|
556
|
+
shouldContinue = true;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
511
560
|
// Log hook execution results for debugging
|
|
512
561
|
if (results.length > 0) {
|
|
513
562
|
this.logger?.debug(
|
|
@@ -521,20 +570,25 @@ export class AIManager {
|
|
|
521
570
|
})),
|
|
522
571
|
);
|
|
523
572
|
}
|
|
573
|
+
|
|
574
|
+
return shouldContinue;
|
|
524
575
|
} catch (error) {
|
|
525
576
|
// Hook execution errors should not interrupt the main workflow
|
|
526
577
|
this.logger?.error("Stop hook execution failed:", error);
|
|
578
|
+
return false;
|
|
527
579
|
}
|
|
528
580
|
}
|
|
529
581
|
|
|
530
582
|
/**
|
|
531
583
|
* Execute PreToolUse hooks before tool execution
|
|
584
|
+
* Returns true if hooks allow tool execution, false if blocked
|
|
532
585
|
*/
|
|
533
586
|
private async executePreToolUseHooks(
|
|
534
587
|
toolName: string,
|
|
535
588
|
toolInput?: Record<string, unknown>,
|
|
536
|
-
|
|
537
|
-
|
|
589
|
+
toolId?: string,
|
|
590
|
+
): Promise<boolean> {
|
|
591
|
+
if (!this.hookManager) return true;
|
|
538
592
|
|
|
539
593
|
try {
|
|
540
594
|
const context: ExtendedHookExecutionContext = {
|
|
@@ -553,6 +607,19 @@ export class AIManager {
|
|
|
553
607
|
context,
|
|
554
608
|
);
|
|
555
609
|
|
|
610
|
+
// Process hook results to handle exit codes and determine if tool should be blocked
|
|
611
|
+
let shouldContinue = true;
|
|
612
|
+
if (results.length > 0) {
|
|
613
|
+
const processResult = this.hookManager.processHookResults(
|
|
614
|
+
"PreToolUse",
|
|
615
|
+
results,
|
|
616
|
+
this.messageManager,
|
|
617
|
+
toolId, // Pass toolId for proper PreToolUse blocking error handling
|
|
618
|
+
JSON.stringify(toolInput || {}, null, 2), // Pass serialized tool parameters
|
|
619
|
+
);
|
|
620
|
+
shouldContinue = !processResult.shouldBlock;
|
|
621
|
+
}
|
|
622
|
+
|
|
556
623
|
// Log hook execution results for debugging
|
|
557
624
|
if (results.length > 0) {
|
|
558
625
|
this.logger?.debug(
|
|
@@ -566,9 +633,12 @@ export class AIManager {
|
|
|
566
633
|
})),
|
|
567
634
|
);
|
|
568
635
|
}
|
|
636
|
+
|
|
637
|
+
return shouldContinue;
|
|
569
638
|
} catch (error) {
|
|
570
639
|
// Hook execution errors should not interrupt the main workflow
|
|
571
640
|
this.logger?.error("PreToolUse hook execution failed:", error);
|
|
641
|
+
return true; // Allow tool execution on hook errors
|
|
572
642
|
}
|
|
573
643
|
}
|
|
574
644
|
|
|
@@ -576,6 +646,7 @@ export class AIManager {
|
|
|
576
646
|
* Execute PostToolUse hooks after tool completion
|
|
577
647
|
*/
|
|
578
648
|
private async executePostToolUseHooks(
|
|
649
|
+
toolId: string,
|
|
579
650
|
toolName: string,
|
|
580
651
|
toolInput?: Record<string, unknown>,
|
|
581
652
|
toolResponse?: ToolResult,
|
|
@@ -600,6 +671,16 @@ export class AIManager {
|
|
|
600
671
|
context,
|
|
601
672
|
);
|
|
602
673
|
|
|
674
|
+
// Process hook results to handle exit codes and update tool results
|
|
675
|
+
if (results.length > 0) {
|
|
676
|
+
this.hookManager.processHookResults(
|
|
677
|
+
"PostToolUse",
|
|
678
|
+
results,
|
|
679
|
+
this.messageManager,
|
|
680
|
+
toolId,
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
|
|
603
684
|
// Log hook execution results for debugging
|
|
604
685
|
if (results.length > 0) {
|
|
605
686
|
this.logger?.debug(
|