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.
Files changed (145) hide show
  1. package/dist/agent.d.ts +3 -6
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +24 -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 +91 -53
  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} +27 -16
  13. package/dist/managers/hookManager.d.ts.map +1 -0
  14. package/dist/{hooks/manager.js → managers/hookManager.js} +112 -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 +20 -15
  18. package/dist/managers/messageManager.d.ts.map +1 -1
  19. package/dist/managers/messageManager.js +19 -25
  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/slashCommandManager.js +5 -2
  25. package/dist/managers/subagentManager.d.ts +7 -12
  26. package/dist/managers/subagentManager.d.ts.map +1 -1
  27. package/dist/managers/subagentManager.js +40 -46
  28. package/dist/managers/toolManager.d.ts +1 -1
  29. package/dist/managers/toolManager.d.ts.map +1 -1
  30. package/dist/services/aiService.d.ts +1 -1
  31. package/dist/services/aiService.d.ts.map +1 -1
  32. package/dist/services/aiService.js +8 -1
  33. package/dist/services/hook.d.ts +56 -0
  34. package/dist/services/hook.d.ts.map +1 -0
  35. package/dist/services/hook.js +276 -0
  36. package/dist/services/session.d.ts +1 -1
  37. package/dist/services/session.d.ts.map +1 -1
  38. package/dist/services/session.js +5 -4
  39. package/dist/tools/taskTool.d.ts.map +1 -1
  40. package/dist/tools/taskTool.js +7 -3
  41. package/dist/tools/todoWriteTool.d.ts.map +1 -1
  42. package/dist/tools/todoWriteTool.js +3 -10
  43. package/dist/types/commands.d.ts +24 -0
  44. package/dist/types/commands.d.ts.map +1 -0
  45. package/dist/types/commands.js +5 -0
  46. package/dist/types/config.d.ts +13 -0
  47. package/dist/types/config.d.ts.map +1 -0
  48. package/dist/types/config.js +5 -0
  49. package/dist/types/core.d.ts +38 -0
  50. package/dist/types/core.d.ts.map +1 -0
  51. package/dist/{types.js → types/core.js} +4 -13
  52. package/dist/{hooks/types.d.ts → types/hooks.d.ts} +2 -1
  53. package/dist/types/hooks.d.ts.map +1 -0
  54. package/dist/types/index.d.ts +20 -0
  55. package/dist/types/index.d.ts.map +1 -0
  56. package/dist/types/index.js +21 -0
  57. package/dist/types/mcp.d.ts +28 -0
  58. package/dist/types/mcp.d.ts.map +1 -0
  59. package/dist/types/mcp.js +5 -0
  60. package/dist/types/messaging.d.ts +80 -0
  61. package/dist/types/messaging.d.ts.map +1 -0
  62. package/dist/types/messaging.js +9 -0
  63. package/dist/types/processes.d.ts +17 -0
  64. package/dist/types/processes.d.ts.map +1 -0
  65. package/dist/types/processes.js +5 -0
  66. package/dist/types/skills.d.ts +78 -0
  67. package/dist/types/skills.d.ts.map +1 -0
  68. package/dist/types/skills.js +17 -0
  69. package/dist/utils/configResolver.d.ts +1 -1
  70. package/dist/utils/configResolver.d.ts.map +1 -1
  71. package/dist/utils/configResolver.js +1 -1
  72. package/dist/utils/configValidator.d.ts +1 -1
  73. package/dist/utils/configValidator.d.ts.map +1 -1
  74. package/dist/utils/configValidator.js +1 -1
  75. package/dist/utils/convertMessagesForAPI.d.ts +1 -1
  76. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  77. package/dist/utils/convertMessagesForAPI.js +1 -8
  78. package/dist/utils/customCommands.d.ts +1 -1
  79. package/dist/utils/customCommands.d.ts.map +1 -1
  80. package/dist/{hooks/matcher.d.ts → utils/hookMatcher.d.ts} +2 -7
  81. package/dist/utils/hookMatcher.d.ts.map +1 -0
  82. package/dist/utils/markdownParser.d.ts +1 -1
  83. package/dist/utils/markdownParser.d.ts.map +1 -1
  84. package/dist/utils/mcpUtils.d.ts +1 -1
  85. package/dist/utils/mcpUtils.d.ts.map +1 -1
  86. package/dist/utils/messageOperations.d.ts +14 -21
  87. package/dist/utils/messageOperations.d.ts.map +1 -1
  88. package/dist/utils/messageOperations.js +37 -20
  89. package/dist/utils/skillParser.d.ts +1 -1
  90. package/dist/utils/skillParser.d.ts.map +1 -1
  91. package/package.json +1 -1
  92. package/src/agent.ts +49 -43
  93. package/src/index.ts +3 -4
  94. package/src/managers/aiManager.ts +241 -160
  95. package/src/managers/backgroundBashManager.ts +1 -1
  96. package/src/{hooks/manager.ts → managers/hookManager.ts} +168 -56
  97. package/src/managers/mcpManager.ts +1 -1
  98. package/src/managers/messageManager.ts +44 -44
  99. package/src/managers/skillManager.ts +1 -1
  100. package/src/managers/slashCommandManager.ts +10 -7
  101. package/src/managers/subagentManager.ts +47 -54
  102. package/src/managers/toolManager.ts +1 -1
  103. package/src/services/aiService.ts +9 -2
  104. package/src/services/hook.ts +360 -0
  105. package/src/services/session.ts +6 -7
  106. package/src/tools/taskTool.ts +13 -5
  107. package/src/tools/todoWriteTool.ts +3 -11
  108. package/src/types/commands.ts +26 -0
  109. package/src/types/config.ts +14 -0
  110. package/src/types/core.ts +49 -0
  111. package/src/{hooks/types.ts → types/hooks.ts} +1 -0
  112. package/src/types/index.ts +23 -0
  113. package/src/types/mcp.ts +31 -0
  114. package/src/types/messaging.ts +102 -0
  115. package/src/types/processes.ts +18 -0
  116. package/src/types/skills.ts +91 -0
  117. package/src/utils/configResolver.ts +1 -1
  118. package/src/utils/configValidator.ts +5 -1
  119. package/src/utils/convertMessagesForAPI.ts +2 -10
  120. package/src/utils/customCommands.ts +1 -1
  121. package/src/{hooks/matcher.ts → utils/hookMatcher.ts} +1 -12
  122. package/src/utils/markdownParser.ts +1 -1
  123. package/src/utils/mcpUtils.ts +1 -1
  124. package/src/utils/messageOperations.ts +56 -42
  125. package/src/utils/skillParser.ts +1 -1
  126. package/dist/hooks/executor.d.ts +0 -56
  127. package/dist/hooks/executor.d.ts.map +0 -1
  128. package/dist/hooks/executor.js +0 -312
  129. package/dist/hooks/index.d.ts +0 -17
  130. package/dist/hooks/index.d.ts.map +0 -1
  131. package/dist/hooks/index.js +0 -14
  132. package/dist/hooks/manager.d.ts.map +0 -1
  133. package/dist/hooks/matcher.d.ts.map +0 -1
  134. package/dist/hooks/settings.d.ts +0 -46
  135. package/dist/hooks/settings.d.ts.map +0 -1
  136. package/dist/hooks/settings.js +0 -100
  137. package/dist/hooks/types.d.ts.map +0 -1
  138. package/dist/types.d.ts +0 -288
  139. package/dist/types.d.ts.map +0 -1
  140. package/src/hooks/executor.ts +0 -440
  141. package/src/hooks/index.ts +0 -52
  142. package/src/hooks/settings.ts +0 -129
  143. package/src/types.ts +0 -357
  144. /package/dist/{hooks/types.js → types/hooks.js} +0 -0
  145. /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 { 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) {
@@ -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
- 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();
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
- 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);
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
- // Set tool start execution state
329
- const toolName = functionToolCall.function?.name || "";
330
- const compactParams = this.generateCompactParams(
331
- toolName,
332
- toolArgs,
333
- );
334
-
335
- this.messageManager.updateToolBlock({
336
- toolId,
337
- args: JSON.stringify(toolArgs, null, 2),
338
- isRunning: true, // isRunning: true
339
- name: toolName,
340
- compactParams,
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
- 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 || "",
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
- 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
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
- // 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,
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
- // 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);
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
- 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;
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
- 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
- }
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
- // 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;
478
+ this.messageManager.addErrorBlock(
479
+ error instanceof Error ? error.message : "Unknown error occurred",
480
+ );
478
481
  } finally {
479
- // Only clear loading state for the initial call
482
+ // Only execute Stop hooks for the initial call
480
483
  if (recursionDepth === 0) {
481
- this.setIsLoading(false);
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
- // Save session before executing Stop hooks
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
- // Execute Stop hooks when AI response cycle completes
487
- await this.executeStopHooks();
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<void> {
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
- ): Promise<void> {
537
- if (!this.hookManager) return;
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(
@@ -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;