( toolUseContext: ToolUseContext, tool: Tool<Input, Output>, toolUseID: string, messageId: string, toolInput: Record<string, unknown>, toolResponse: Output, requestId: string | undefined, mcpServerType: McpServerType, mcpServerBaseUrl: string | undefined, )
| 37 | | { updatedMCPToolOutput: Output } |
| 38 | |
| 39 | export async function* runPostToolUseHooks<Input extends AnyObject, Output>( |
| 40 | toolUseContext: ToolUseContext, |
| 41 | tool: Tool<Input, Output>, |
| 42 | toolUseID: string, |
| 43 | messageId: string, |
| 44 | toolInput: Record<string, unknown>, |
| 45 | toolResponse: Output, |
| 46 | requestId: string | undefined, |
| 47 | mcpServerType: McpServerType, |
| 48 | mcpServerBaseUrl: string | undefined, |
| 49 | ): AsyncGenerator<PostToolUseHooksResult<Output>> { |
| 50 | const postToolStartTime = Date.now() |
| 51 | try { |
| 52 | const appState = toolUseContext.getAppState() |
| 53 | const permissionMode = appState.toolPermissionContext.mode |
| 54 | |
| 55 | let toolOutput = toolResponse |
| 56 | for await (const result of executePostToolHooks( |
| 57 | tool.name, |
| 58 | toolUseID, |
| 59 | toolInput, |
| 60 | toolOutput, |
| 61 | toolUseContext, |
| 62 | permissionMode, |
| 63 | toolUseContext.abortController.signal, |
| 64 | )) { |
| 65 | try { |
| 66 | // Check if we were aborted during hook execution |
| 67 | // IMPORTANT: We emit a cancelled event per hook |
| 68 | if ( |
| 69 | result.message?.type === 'attachment' && |
| 70 | result.message.attachment.type === 'hook_cancelled' |
| 71 | ) { |
| 72 | logEvent('tengu_post_tool_hooks_cancelled', { |
| 73 | toolName: sanitizeToolNameForAnalytics(tool.name), |
| 74 | |
| 75 | queryChainId: toolUseContext.queryTracking |
| 76 | ?.chainId as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| 77 | queryDepth: toolUseContext.queryTracking?.depth, |
| 78 | }) |
| 79 | yield { |
| 80 | message: createAttachmentMessage({ |
| 81 | type: 'hook_cancelled', |
| 82 | hookName: `PostToolUse:${tool.name}`, |
| 83 | toolUseID, |
| 84 | hookEvent: 'PostToolUse', |
| 85 | }), |
| 86 | } |
| 87 | continue |
| 88 | } |
| 89 | |
| 90 | // For JSON {decision:"block"} hooks, executeHooks yields two results: |
| 91 | // {blockingError} and {message: hook_blocking_error attachment}. The |
| 92 | // blockingError path below creates that same attachment, so skip it |
| 93 | // here to avoid displaying the block reason twice (#31301). The |
| 94 | // exit-code-2 path only yields {blockingError}, so it's unaffected. |
| 95 | if ( |
| 96 | result.message && |
no test coverage detected