wave-agent-sdk 0.0.5 → 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.
Files changed (140) hide show
  1. package/dist/agent.d.ts +3 -6
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +21 -21
  4. package/dist/index.d.ts +3 -2
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +3 -3
  7. package/dist/managers/aiManager.d.ts +4 -2
  8. package/dist/managers/aiManager.d.ts.map +1 -1
  9. package/dist/managers/aiManager.js +84 -47
  10. package/dist/managers/backgroundBashManager.d.ts +1 -1
  11. package/dist/managers/backgroundBashManager.d.ts.map +1 -1
  12. package/dist/{hooks/manager.d.ts → managers/hookManager.d.ts} +26 -7
  13. package/dist/managers/hookManager.d.ts.map +1 -0
  14. package/dist/{hooks/manager.js → managers/hookManager.js} +107 -17
  15. package/dist/managers/mcpManager.d.ts +1 -1
  16. package/dist/managers/mcpManager.d.ts.map +1 -1
  17. package/dist/managers/messageManager.d.ts +17 -4
  18. package/dist/managers/messageManager.d.ts.map +1 -1
  19. package/dist/managers/messageManager.js +13 -5
  20. package/dist/managers/skillManager.d.ts +1 -1
  21. package/dist/managers/skillManager.d.ts.map +1 -1
  22. package/dist/managers/slashCommandManager.d.ts +1 -1
  23. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  24. package/dist/managers/subagentManager.d.ts +7 -12
  25. package/dist/managers/subagentManager.d.ts.map +1 -1
  26. package/dist/managers/subagentManager.js +39 -45
  27. package/dist/managers/toolManager.d.ts +1 -1
  28. package/dist/managers/toolManager.d.ts.map +1 -1
  29. package/dist/services/aiService.d.ts +1 -1
  30. package/dist/services/aiService.d.ts.map +1 -1
  31. package/dist/services/aiService.js +8 -1
  32. package/dist/services/hook.d.ts +56 -0
  33. package/dist/services/hook.d.ts.map +1 -0
  34. package/dist/services/hook.js +276 -0
  35. package/dist/services/session.d.ts +1 -1
  36. package/dist/services/session.d.ts.map +1 -1
  37. package/dist/services/session.js +5 -4
  38. package/dist/tools/taskTool.d.ts.map +1 -1
  39. package/dist/tools/taskTool.js +7 -3
  40. package/dist/types/commands.d.ts +24 -0
  41. package/dist/types/commands.d.ts.map +1 -0
  42. package/dist/types/commands.js +5 -0
  43. package/dist/types/config.d.ts +13 -0
  44. package/dist/types/config.d.ts.map +1 -0
  45. package/dist/types/config.js +5 -0
  46. package/dist/types/core.d.ts +38 -0
  47. package/dist/types/core.d.ts.map +1 -0
  48. package/dist/{types.js → types/core.js} +4 -13
  49. package/dist/{hooks/types.d.ts → types/hooks.d.ts} +2 -1
  50. package/dist/types/hooks.d.ts.map +1 -0
  51. package/dist/types/index.d.ts +20 -0
  52. package/dist/types/index.d.ts.map +1 -0
  53. package/dist/types/index.js +21 -0
  54. package/dist/types/mcp.d.ts +28 -0
  55. package/dist/types/mcp.d.ts.map +1 -0
  56. package/dist/types/mcp.js +5 -0
  57. package/dist/types/messaging.d.ts +80 -0
  58. package/dist/types/messaging.d.ts.map +1 -0
  59. package/dist/types/messaging.js +5 -0
  60. package/dist/types/processes.d.ts +17 -0
  61. package/dist/types/processes.d.ts.map +1 -0
  62. package/dist/types/processes.js +5 -0
  63. package/dist/types/skills.d.ts +78 -0
  64. package/dist/types/skills.d.ts.map +1 -0
  65. package/dist/types/skills.js +17 -0
  66. package/dist/utils/configResolver.d.ts +1 -1
  67. package/dist/utils/configResolver.d.ts.map +1 -1
  68. package/dist/utils/configResolver.js +1 -1
  69. package/dist/utils/configValidator.d.ts +1 -1
  70. package/dist/utils/configValidator.d.ts.map +1 -1
  71. package/dist/utils/configValidator.js +1 -1
  72. package/dist/utils/convertMessagesForAPI.d.ts +1 -1
  73. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  74. package/dist/utils/customCommands.d.ts +1 -1
  75. package/dist/utils/customCommands.d.ts.map +1 -1
  76. package/dist/{hooks/matcher.d.ts → utils/hookMatcher.d.ts} +1 -1
  77. package/dist/utils/hookMatcher.d.ts.map +1 -0
  78. package/dist/utils/markdownParser.d.ts +1 -1
  79. package/dist/utils/markdownParser.d.ts.map +1 -1
  80. package/dist/utils/mcpUtils.d.ts +1 -1
  81. package/dist/utils/mcpUtils.d.ts.map +1 -1
  82. package/dist/utils/messageOperations.d.ts +6 -1
  83. package/dist/utils/messageOperations.d.ts.map +1 -1
  84. package/dist/utils/messageOperations.js +16 -0
  85. package/dist/utils/skillParser.d.ts +1 -1
  86. package/dist/utils/skillParser.d.ts.map +1 -1
  87. package/package.json +1 -1
  88. package/src/agent.ts +49 -43
  89. package/src/index.ts +3 -4
  90. package/src/managers/aiManager.ts +240 -158
  91. package/src/managers/backgroundBashManager.ts +1 -1
  92. package/src/{hooks/manager.ts → managers/hookManager.ts} +159 -26
  93. package/src/managers/mcpManager.ts +1 -1
  94. package/src/managers/messageManager.ts +36 -6
  95. package/src/managers/skillManager.ts +1 -1
  96. package/src/managers/slashCommandManager.ts +5 -1
  97. package/src/managers/subagentManager.ts +46 -53
  98. package/src/managers/toolManager.ts +1 -1
  99. package/src/services/aiService.ts +9 -2
  100. package/src/services/hook.ts +360 -0
  101. package/src/services/session.ts +6 -7
  102. package/src/tools/taskTool.ts +13 -5
  103. package/src/types/commands.ts +26 -0
  104. package/src/types/config.ts +14 -0
  105. package/src/types/core.ts +49 -0
  106. package/src/{hooks/types.ts → types/hooks.ts} +1 -0
  107. package/src/types/index.ts +23 -0
  108. package/src/types/mcp.ts +31 -0
  109. package/src/types/messaging.ts +103 -0
  110. package/src/types/processes.ts +18 -0
  111. package/src/types/skills.ts +91 -0
  112. package/src/utils/configResolver.ts +1 -1
  113. package/src/utils/configValidator.ts +5 -1
  114. package/src/utils/convertMessagesForAPI.ts +1 -1
  115. package/src/utils/customCommands.ts +1 -1
  116. package/src/utils/markdownParser.ts +1 -1
  117. package/src/utils/mcpUtils.ts +1 -1
  118. package/src/utils/messageOperations.ts +20 -1
  119. package/src/utils/skillParser.ts +1 -1
  120. package/dist/hooks/executor.d.ts +0 -56
  121. package/dist/hooks/executor.d.ts.map +0 -1
  122. package/dist/hooks/executor.js +0 -312
  123. package/dist/hooks/index.d.ts +0 -17
  124. package/dist/hooks/index.d.ts.map +0 -1
  125. package/dist/hooks/index.js +0 -14
  126. package/dist/hooks/manager.d.ts.map +0 -1
  127. package/dist/hooks/matcher.d.ts.map +0 -1
  128. package/dist/hooks/settings.d.ts +0 -46
  129. package/dist/hooks/settings.d.ts.map +0 -1
  130. package/dist/hooks/settings.js +0 -100
  131. package/dist/hooks/types.d.ts.map +0 -1
  132. package/dist/types.d.ts +0 -288
  133. package/dist/types.d.ts.map +0 -1
  134. package/src/hooks/executor.ts +0 -440
  135. package/src/hooks/index.ts +0 -52
  136. package/src/hooks/settings.ts +0 -129
  137. /package/dist/{hooks/types.js → types/hooks.js} +0 -0
  138. /package/dist/{hooks/matcher.js → utils/hookMatcher.js} +0 -0
  139. /package/src/{types.ts → types/index.ts.backup} +0 -0
  140. /package/src/{hooks/matcher.ts → utils/hookMatcher.ts} +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 { Logger, GatewayConfig, ModelConfig, Usage } from "../types.js";
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 "../hooks/index.js";
12
- import type { ExtendedHookExecutionContext } from "../hooks/types.js";
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
- // Create new AbortController
226
- const abortController = new AbortController();
227
- this.abortController = abortController;
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
- // Create separate AbortController for tool execution
230
- const toolAbortController = new AbortController();
231
- this.toolAbortController = toolAbortController;
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) {
@@ -295,139 +311,148 @@ export class AIManager {
295
311
  }
296
312
 
297
313
  if (toolCalls.length > 0) {
298
- for (const functionToolCall of toolCalls) {
299
- const toolId = functionToolCall.id || "";
300
- // Execute tool
301
- try {
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();
314
+ // Execute all tools in parallel using Promise.all
315
+ const toolExecutionPromises = toolCalls.map(
316
+ async (functionToolCall) => {
317
+ const toolId = functionToolCall.id || "";
313
318
 
314
- if (!argsString || argsString === "") {
315
- // Tool without parameters, use empty object
316
- toolArgs = {};
317
- } else {
318
- try {
319
- toolArgs = JSON.parse(argsString);
320
- } catch (parseError) {
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);
319
+ try {
320
+ // Check if already interrupted, skip tool execution if so
321
+ if (
322
+ abortController.signal.aborted ||
323
+ toolAbortController.signal.aborted
324
+ ) {
325
+ return;
325
326
  }
326
- }
327
-
328
- // Set tool start execution state
329
- const toolName = functionToolCall.function?.name || "";
330
- const compactParams = this.generateCompactParams(
331
- toolName,
332
- toolArgs,
333
- );
334
327
 
335
- this.messageManager.updateToolBlock({
336
- toolId,
337
- args: JSON.stringify(toolArgs, null, 2),
338
- isRunning: true, // isRunning: true
339
- name: toolName,
340
- compactParams,
341
- });
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
+ }
344
+ }
342
345
 
343
- try {
344
- // Execute PreToolUse hooks before tool execution
345
- await this.executePreToolUseHooks(toolName, toolArgs);
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 || "",
346
+ // Set tool start execution state
347
+ const toolName = functionToolCall.function?.name || "";
348
+ const compactParams = this.generateCompactParams(
349
+ toolName,
357
350
  toolArgs,
358
- context,
359
351
  );
360
352
 
361
- // Update message state - tool execution completed
362
353
  this.messageManager.updateToolBlock({
363
354
  toolId,
364
355
  args: JSON.stringify(toolArgs, null, 2),
365
- result:
366
- toolResult.content ||
367
- (toolResult.error ? `Error: ${toolResult.error}` : ""),
368
- success: toolResult.success,
369
- error: toolResult.error,
370
- isRunning: false, // isRunning: false
356
+ isRunning: true, // isRunning: true
371
357
  name: toolName,
372
- shortResult: toolResult.shortResult,
373
358
  compactParams,
374
359
  });
375
360
 
376
- // If tool returns diff information, add diff block
377
- if (
378
- toolResult.success &&
379
- toolResult.diffResult &&
380
- toolResult.filePath
381
- ) {
382
- this.messageManager.addDiffBlock(
383
- toolResult.filePath,
384
- toolResult.diffResult,
361
+ try {
362
+ // Execute PreToolUse hooks before tool execution
363
+ const shouldExecuteTool = await this.executePreToolUseHooks(
364
+ toolName,
365
+ toolArgs,
366
+ toolId,
385
367
  );
386
- }
387
368
 
388
- // Execute PostToolUse hooks after successful tool completion
389
- await this.executePostToolUseHooks(
390
- toolName,
391
- toolArgs,
392
- toolResult,
393
- );
394
- } catch (toolError) {
395
- const errorMessage =
396
- toolError instanceof Error
397
- ? toolError.message
398
- : String(toolError);
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
+ );
399
390
 
400
- this.messageManager.updateToolBlock({
401
- toolId,
402
- args: JSON.stringify(toolArgs, null, 2),
403
- result: `Tool execution failed: ${errorMessage}`,
404
- success: false,
405
- error: errorMessage,
406
- isRunning: false,
407
- name: toolName,
408
- compactParams,
409
- });
410
- }
411
- } catch (parseError) {
412
- // Check if it's a parsing error due to interruption
413
- const isAborted =
414
- abortController.signal.aborted ||
415
- toolAbortController.signal.aborted;
416
-
417
- if (isAborted) {
418
- // If interrupted, return directly without showing error
419
- return;
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
+ );
420
450
  }
451
+ },
452
+ );
421
453
 
422
- const errorMessage =
423
- parseError instanceof Error
424
- ? parseError.message
425
- : String(parseError);
426
- this.messageManager.addErrorBlock(
427
- `Failed to parse tool arguments for ${functionToolCall.function?.name}: ${errorMessage}`,
428
- );
429
- }
430
- }
454
+ // Wait for all tools to complete execution in parallel
455
+ await Promise.all(toolExecutionPromises);
431
456
  }
432
457
 
433
458
  // Handle token statistics and message compression
@@ -439,12 +464,6 @@ export class AIManager {
439
464
  const isCurrentlyAborted =
440
465
  abortController.signal.aborted || toolAbortController.signal.aborted;
441
466
 
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
467
  if (!isCurrentlyAborted) {
449
468
  // Recursively call AI service, increment recursion depth, and pass same configuration
450
469
  await this.sendAIMessage({
@@ -453,47 +472,57 @@ export class AIManager {
453
472
  allowedTools,
454
473
  });
455
474
  }
456
- } else {
457
- // Clear abort controller when no tool operations
458
- this.abortController = null;
459
- this.toolAbortController = null;
460
475
  }
461
476
  } catch (error) {
462
- // Check if error is due to user interrupt operation
463
- const isAborted =
464
- abortController.signal.aborted ||
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;
477
+ this.messageManager.addErrorBlock(
478
+ error instanceof Error ? error.message : "Unknown error occurred",
479
+ );
478
480
  } finally {
479
- // Only clear loading state for the initial call
481
+ // Only execute Stop hooks for the initial call
480
482
  if (recursionDepth === 0) {
481
- this.setIsLoading(false);
483
+ // Execute Stop hooks only if the operation was not aborted
484
+ const isCurrentlyAborted =
485
+ abortController.signal.aborted || toolAbortController.signal.aborted;
482
486
 
483
- // Save session before executing Stop hooks
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
+ }
506
+
507
+ // Save session after all operations (including continuation) are complete
484
508
  await this.messageManager.saveSession();
485
509
 
486
- // Execute Stop hooks when AI response cycle completes
487
- await this.executeStopHooks();
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);
488
516
  }
489
517
  }
490
518
  }
491
519
 
492
520
  /**
493
521
  * Execute Stop hooks when AI response cycle completes
522
+ * @returns Promise<boolean> - true if should continue conversation, false if should stop
494
523
  */
495
- private async executeStopHooks(): Promise<void> {
496
- if (!this.hookManager) return;
524
+ private async executeStopHooks(): Promise<boolean> {
525
+ if (!this.hookManager) return false;
497
526
 
498
527
  try {
499
528
  const context: ExtendedHookExecutionContext = {
@@ -508,6 +537,25 @@ export class AIManager {
508
537
 
509
538
  const results = await this.hookManager.executeHooks("Stop", context);
510
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
+
511
559
  // Log hook execution results for debugging
512
560
  if (results.length > 0) {
513
561
  this.logger?.debug(
@@ -521,20 +569,25 @@ export class AIManager {
521
569
  })),
522
570
  );
523
571
  }
572
+
573
+ return shouldContinue;
524
574
  } catch (error) {
525
575
  // Hook execution errors should not interrupt the main workflow
526
576
  this.logger?.error("Stop hook execution failed:", error);
577
+ return false;
527
578
  }
528
579
  }
529
580
 
530
581
  /**
531
582
  * Execute PreToolUse hooks before tool execution
583
+ * Returns true if hooks allow tool execution, false if blocked
532
584
  */
533
585
  private async executePreToolUseHooks(
534
586
  toolName: string,
535
587
  toolInput?: Record<string, unknown>,
536
- ): Promise<void> {
537
- if (!this.hookManager) return;
588
+ toolId?: string,
589
+ ): Promise<boolean> {
590
+ if (!this.hookManager) return true;
538
591
 
539
592
  try {
540
593
  const context: ExtendedHookExecutionContext = {
@@ -553,6 +606,18 @@ export class AIManager {
553
606
  context,
554
607
  );
555
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
+
556
621
  // Log hook execution results for debugging
557
622
  if (results.length > 0) {
558
623
  this.logger?.debug(
@@ -566,9 +631,12 @@ export class AIManager {
566
631
  })),
567
632
  );
568
633
  }
634
+
635
+ return shouldContinue;
569
636
  } catch (error) {
570
637
  // Hook execution errors should not interrupt the main workflow
571
638
  this.logger?.error("PreToolUse hook execution failed:", error);
639
+ return true; // Allow tool execution on hook errors
572
640
  }
573
641
  }
574
642
 
@@ -576,6 +644,7 @@ export class AIManager {
576
644
  * Execute PostToolUse hooks after tool completion
577
645
  */
578
646
  private async executePostToolUseHooks(
647
+ toolId: string,
579
648
  toolName: string,
580
649
  toolInput?: Record<string, unknown>,
581
650
  toolResponse?: ToolResult,
@@ -600,6 +669,19 @@ export class AIManager {
600
669
  context,
601
670
  );
602
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
+
603
685
  // Log hook execution results for debugging
604
686
  if (results.length > 0) {
605
687
  this.logger?.debug(
@@ -1,5 +1,5 @@
1
1
  import { spawn } from "child_process";
2
- import type { BackgroundShell } from "../types.js";
2
+ import type { BackgroundShell } from "../types/index.js";
3
3
 
4
4
  export interface BackgroundBashManagerCallbacks {
5
5
  onShellsChange?: (shells: BackgroundShell[]) => void;