/ utils / hooks / registerFrontmatterHooks.ts
registerFrontmatterHooks.ts
 1  import { HOOK_EVENTS, type HookEvent } from 'src/entrypoints/agentSdkTypes.js'
 2  import type { AppState } from 'src/state/AppState.js'
 3  import { logForDebugging } from '../debug.js'
 4  import type { HooksSettings } from '../settings/types.js'
 5  import { addSessionHook } from './sessionHooks.js'
 6  
 7  /**
 8   * Register hooks from frontmatter (agent or skill) into session-scoped hooks.
 9   * These hooks will be active for the duration of the session/agent and cleaned up
10   * when the session/agent ends.
11   *
12   * @param setAppState Function to update app state
13   * @param sessionId Session ID to scope the hooks (agent ID for agents, session ID for skills)
14   * @param hooks The hooks settings from frontmatter
15   * @param sourceName Human-readable source name for logging (e.g., "agent 'my-agent'")
16   * @param isAgent If true, converts Stop hooks to SubagentStop (since subagents trigger SubagentStop, not Stop)
17   */
18  export function registerFrontmatterHooks(
19    setAppState: (updater: (prev: AppState) => AppState) => void,
20    sessionId: string,
21    hooks: HooksSettings,
22    sourceName: string,
23    isAgent: boolean = false,
24  ): void {
25    if (!hooks || Object.keys(hooks).length === 0) {
26      return
27    }
28  
29    let hookCount = 0
30  
31    for (const event of HOOK_EVENTS) {
32      const matchers = hooks[event]
33      if (!matchers || matchers.length === 0) {
34        continue
35      }
36  
37      // For agents, convert Stop hooks to SubagentStop since that's what fires when an agent completes
38      // (executeStopHooks uses SubagentStop when called with an agentId)
39      let targetEvent: HookEvent = event
40      if (isAgent && event === 'Stop') {
41        targetEvent = 'SubagentStop'
42        logForDebugging(
43          `Converting Stop hook to SubagentStop for ${sourceName} (subagents trigger SubagentStop)`,
44        )
45      }
46  
47      for (const matcherConfig of matchers) {
48        const matcher = matcherConfig.matcher ?? ''
49        const hooksArray = matcherConfig.hooks
50  
51        if (!hooksArray || hooksArray.length === 0) {
52          continue
53        }
54  
55        for (const hook of hooksArray) {
56          addSessionHook(setAppState, sessionId, targetEvent, matcher, hook)
57          hookCount++
58        }
59      }
60    }
61  
62    if (hookCount > 0) {
63      logForDebugging(
64        `Registered ${hookCount} frontmatter hook(s) from ${sourceName} for session ${sessionId}`,
65      )
66    }
67  }