( appState: AppState | undefined, sessionId: string, hookEvent: HookEvent, )
| 1490 | } |
| 1491 | |
| 1492 | function getHooksConfig( |
| 1493 | appState: AppState | undefined, |
| 1494 | sessionId: string, |
| 1495 | hookEvent: HookEvent, |
| 1496 | ): Array< |
| 1497 | | HookMatcher |
| 1498 | | HookCallbackMatcher |
| 1499 | | FunctionHookMatcher |
| 1500 | | PluginHookMatcher |
| 1501 | | SkillHookMatcher |
| 1502 | | SessionDerivedHookMatcher |
| 1503 | > { |
| 1504 | // HookMatcher is a zod-stripped {matcher, hooks} so snapshot matchers can be |
| 1505 | // pushed directly without re-wrapping. |
| 1506 | const hooks: Array< |
| 1507 | | HookMatcher |
| 1508 | | HookCallbackMatcher |
| 1509 | | FunctionHookMatcher |
| 1510 | | PluginHookMatcher |
| 1511 | | SkillHookMatcher |
| 1512 | | SessionDerivedHookMatcher |
| 1513 | > = [...(getHooksConfigFromSnapshot()?.[hookEvent] ?? [])] |
| 1514 | |
| 1515 | // Check if only managed hooks should run (used for both registered and session hooks) |
| 1516 | const managedOnly = shouldAllowManagedHooksOnly() |
| 1517 | |
| 1518 | // Process registered hooks (SDK callbacks and plugin native hooks) |
| 1519 | const registeredHooks = getRegisteredHooks()?.[hookEvent] |
| 1520 | if (registeredHooks) { |
| 1521 | for (const matcher of registeredHooks) { |
| 1522 | // Skip plugin hooks when restricted to managed hooks only |
| 1523 | // Plugin hooks have pluginRoot set, SDK callbacks do not |
| 1524 | if (managedOnly && 'pluginRoot' in matcher) { |
| 1525 | continue |
| 1526 | } |
| 1527 | hooks.push(matcher) |
| 1528 | } |
| 1529 | } |
| 1530 | |
| 1531 | // Merge session hooks for the current session only |
| 1532 | // Function hooks (like structured output enforcement) must be scoped to their session |
| 1533 | // to prevent hooks from one agent leaking to another (e.g., verification agent to main agent) |
| 1534 | // Skip session hooks entirely when allowManagedHooksOnly is set — |
| 1535 | // this prevents frontmatter hooks from agents/skills from bypassing the policy. |
| 1536 | // strictPluginOnlyCustomization does NOT block here — it gates at the |
| 1537 | // REGISTRATION sites (runAgent.ts:526 for agent frontmatter hooks) where |
| 1538 | // agentDefinition.source is known. A blanket block here would also kill |
| 1539 | // plugin-provided agents' frontmatter hooks, which is too broad. |
| 1540 | // Also skip if appState not provided (for backwards compatibility) |
| 1541 | if (!managedOnly && appState !== undefined) { |
| 1542 | const sessionHooks = getSessionHooks(appState, sessionId, hookEvent).get( |
| 1543 | hookEvent, |
| 1544 | ) |
| 1545 | if (sessionHooks) { |
| 1546 | // SessionDerivedHookMatcher already includes optional skillRoot |
| 1547 | for (const matcher of sessionHooks) { |
| 1548 | hooks.push(matcher) |
| 1549 | } |
no test coverage detected