( tool: ToolType, messageId: string, source: PermissionRejectionSource | 'config', waitMs: number | undefined, )
| 150 | |
| 151 | // Rejections share a single event name, differentiated by metadata fields |
| 152 | function logRejectionEvent( |
| 153 | tool: ToolType, |
| 154 | messageId: string, |
| 155 | source: PermissionRejectionSource | 'config', |
| 156 | waitMs: number | undefined, |
| 157 | ): void { |
| 158 | if (source === 'config') { |
| 159 | // Denied by denylist in settings |
| 160 | logEvent( |
| 161 | 'tengu_tool_use_denied_in_config', |
| 162 | baseMetadata(messageId, tool.name, undefined), |
| 163 | ) |
| 164 | return |
| 165 | } |
| 166 | logEvent('tengu_tool_use_rejected_in_prompt', { |
| 167 | ...baseMetadata(messageId, tool.name, waitMs), |
| 168 | // Distinguish hook rejections from user rejections via separate fields |
| 169 | ...(source.type === 'hook' |
| 170 | ? { isHook: true } |
| 171 | : { |
| 172 | hasFeedback: |
| 173 | source.type === 'user_reject' ? source.hasFeedback : false, |
| 174 | }), |
| 175 | }) |
| 176 | } |
| 177 | |
| 178 | // Single entry point for all permission decision logging. Called by permission |
| 179 | // handlers after every approve/reject. Fans out to: analytics events, OTel |
no test coverage detected