( lastMessage: AssistantMessage, toolUseContext?: ToolUseContext, timeoutMs: number = TOOL_HOOK_EXECUTION_TIMEOUT_MS, )
| 3592 | } |
| 3593 | |
| 3594 | export async function executeStopFailureHooks( |
| 3595 | lastMessage: AssistantMessage, |
| 3596 | toolUseContext?: ToolUseContext, |
| 3597 | timeoutMs: number = TOOL_HOOK_EXECUTION_TIMEOUT_MS, |
| 3598 | ): Promise<void> { |
| 3599 | const appState = toolUseContext?.getAppState() |
| 3600 | // executeHooksOutsideREPL hardcodes main sessionId (:2738). Agent frontmatter |
| 3601 | // hooks (registerFrontmatterHooks) key by agentId; gating with agentId here |
| 3602 | // would pass the gate but fail execution. Align gate with execution. |
| 3603 | const sessionId = getSessionId() |
| 3604 | if (!hasHookForEvent('StopFailure', appState, sessionId)) return |
| 3605 | |
| 3606 | const lastAssistantText = |
| 3607 | extractTextContent(lastMessage.message.content, '\n').trim() || undefined |
| 3608 | |
| 3609 | // Some createAssistantAPIErrorMessage call sites omit `error` (e.g. |
| 3610 | // image-size at errors.ts:431). Default to 'unknown' so matcher filtering |
| 3611 | // at getMatchingHooks:1525 always applies. |
| 3612 | const error = lastMessage.error ?? 'unknown' |
| 3613 | const hookInput: StopFailureHookInput = { |
| 3614 | ...createBaseHookInput(undefined, undefined, toolUseContext), |
| 3615 | hook_event_name: 'StopFailure', |
| 3616 | error, |
| 3617 | error_details: lastMessage.errorDetails, |
| 3618 | last_assistant_message: lastAssistantText, |
| 3619 | } |
| 3620 | |
| 3621 | await executeHooksOutsideREPL({ |
| 3622 | getAppState: toolUseContext?.getAppState, |
| 3623 | hookInput, |
| 3624 | timeoutMs, |
| 3625 | matchQuery: error, |
| 3626 | }) |
| 3627 | } |
| 3628 | |
| 3629 | /** |
| 3630 | * Execute stop hooks if configured |
no test coverage detected