- Inicio
- Documentation
- Configuración
- Hooks
Hooks
Este documento describe el código actual del subsistema de hooks en src/extensibility/hooks/*.
Estado actual en tiempo de ejecución
Sección titulada «Estado actual en tiempo de ejecución»El paquete de hooks (src/extensibility/hooks/) sigue exportándose y siendo utilizable como superficie de API, pero el tiempo de ejecución predeterminado de la CLI ahora inicializa la ruta del ejecutor de extensiones. En el flujo de inicio actual:
--hookse trata como un alias de--extension(las rutas de CLI se fusionan enadditionalExtensionPaths)- las herramientas están envueltas por
ExtensionToolWrapper, no porHookToolWrapper - las transformaciones de contexto y las emisiones de ciclo de vida pasan por
ExtensionRunner
Por lo tanto, este archivo documenta la implementación del subsistema de hooks en sí (tipos/cargador/ejecutor/envoltorio), incluyendo el comportamiento heredado y las restricciones.
Archivos clave
Sección titulada «Archivos clave»src/extensibility/hooks/types.ts— contexto de hook, tipos de evento y contratos de resultadosrc/extensibility/hooks/loader.ts— carga de módulos y puente de descubrimiento de hookssrc/extensibility/hooks/runner.ts— despacho de eventos, búsqueda de comandos y señalización de erroressrc/extensibility/hooks/tool-wrapper.ts— envoltorio de intercepción previo/posterior de herramientassrc/extensibility/hooks/index.ts— exportaciones/reexportaciones
Qué es un módulo hook
Sección titulada «Qué es un módulo hook»Un módulo hook debe exportar por defecto una fábrica:
import type { HookAPI } from "@f5-sales-demo/xcsh/hooks";
export default function hook(pi: HookAPI): void { pi.on("tool_call", async (event, ctx) => { if (event.toolName === "bash" && String(event.input.command ?? "").includes("rm -rf")) { return { block: true, reason: "blocked by policy" }; } });}La fábrica puede:
- registrar manejadores de eventos con
pi.on(...) - enviar mensajes personalizados persistentes con
pi.sendMessage(...) - persistir estado no-LLM con
pi.appendEntry(...) - registrar comandos slash mediante
pi.registerCommand(...) - registrar renderizadores de mensajes personalizados mediante
pi.registerMessageRenderer(...) - ejecutar comandos de shell mediante
pi.exec(...)
Descubrimiento y carga
Sección titulada «Descubrimiento y carga»discoverAndLoadHooks(configuredPaths, cwd) realiza lo siguiente:
- Carga los hooks descubiertos desde el registro de capacidades (
loadCapability("hooks")) - Añade las rutas configuradas explícitamente (deduplicadas por ruta absoluta)
- Llama a
loadHooks(allPaths, cwd)
loadHooks luego importa cada ruta y espera una función default.
Resolución de rutas
Sección titulada «Resolución de rutas»loader.ts resuelve las rutas de hooks de la siguiente manera:
- ruta absoluta: se usa tal cual
- ruta
~: expandida - ruta relativa: resuelta contra
cwd
Discrepancia heredada importante
Sección titulada «Discrepancia heredada importante»Los proveedores de descubrimiento para hookCapability siguen modelando archivos de hook de estilo shell pre/post (por ejemplo, .claude/hooks/pre/*, .xcsh/.../hooks/pre/*).
El cargador de hooks aquí usa importación dinámica de módulos y requiere una fábrica de hooks JS/TS por defecto. Si una ruta de hook descubierta no es importable como módulo, la carga falla y se reporta en LoadHooksResult.errors.
Superficies de eventos
Sección titulada «Superficies de eventos»Los eventos de hook están fuertemente tipados en types.ts.
Eventos de sesión
Sección titulada «Eventos de sesión»session_startsession_before_switch→ puede retornar{ cancel?: boolean }session_switchsession_before_branch→ puede retornar{ cancel?: boolean; skipConversationRestore?: boolean }session_branchsession_before_compact→ puede retornar{ cancel?: boolean; compaction?: CompactionResult }session.compacting→ puede retornar{ context?: string[]; prompt?: string; preserveData?: Record<string, unknown> }session_compactsession_before_tree→ puede retornar{ cancel?: boolean; summary?: { summary: string; details?: unknown } }session_treesession_shutdown
Eventos de agente/contexto
Sección titulada «Eventos de agente/contexto»context→ puede retornar{ messages?: Message[] }before_agent_start→ puede retornar{ message?: { customType; content; display; details } }agent_startagent_endturn_startturn_endauto_compaction_startauto_compaction_endauto_retry_startauto_retry_endttsr_triggeredtodo_reminder
Eventos de herramienta (modelo previo/posterior)
Sección titulada «Eventos de herramienta (modelo previo/posterior)»tool_call(pre-ejecución) → puede retornar{ block?: boolean; reason?: string }tool_result(post-ejecución) → puede retornar{ content?; details?; isError? }
Este es el modelo de intercepción previo/posterior central del subsistema de hooks.
Hook tool interception flow
tool_call handlers │ ├─ any { block: true }? ── yes ──> throw (tool blocked) │ └─ no │ ▼ execute underlying tool │ ├─ success ──> tool_result handlers can override { content, details } │ └─ error ──> emit tool_result(isError=true) then rethrow original errorModelo de ejecución y semántica de mutación
Sección titulada «Modelo de ejecución y semántica de mutación»1) Pre-ejecución: tool_call
Sección titulada «1) Pre-ejecución: tool_call»HookToolWrapper.execute() emite tool_call antes de la ejecución de la herramienta.
- si algún manejador retorna
{ block: true }, la ejecución se detiene - si el manejador lanza una excepción, el envoltorio falla de forma cerrada y bloquea la ejecución
- el
reasonretornado se convierte en el texto del error lanzado
2) Ejecución de la herramienta
Sección titulada «2) Ejecución de la herramienta»La herramienta subyacente se ejecuta normalmente si no está bloqueada.
3) Post-ejecución: tool_result
Sección titulada «3) Post-ejecución: tool_result»Tras el éxito, el envoltorio emite tool_result con:
toolName,toolCallId,inputcontentdetailsisError: false
Si el manejador retorna sobreescrituras:
contentpuede reemplazar el contenido del resultadodetailspuede reemplazar los detalles del resultado
En caso de fallo de la herramienta, el envoltorio emite tool_result con isError: true y el contenido del texto de error, luego relanza el error original.
Qué pueden mutar los hooks
Sección titulada «Qué pueden mutar los hooks»- el contexto LLM para una sola llamada mediante
context(cadena de reemplazo demessages) - el contenido/detalles de la salida de la herramienta en llamadas exitosas (ruta
tool_result) - el mensaje inyectado pre-agente mediante
before_agent_start - el comportamiento de cancelación/compactación personalizada/árbol mediante
session_before_*ysession.compacting
Qué no pueden mutar los hooks en esta implementación
Sección titulada «Qué no pueden mutar los hooks en esta implementación»- los parámetros de entrada de la herramienta en su lugar (solo bloquear/permitir en
tool_call) - la continuación de la ejecución tras errores lanzados por la herramienta (la ruta de error relanza)
- el estado final de éxito/error en el comportamiento del envoltorio (el
isErrorretornado está tipado pero no es aplicado porHookToolWrapper)
Ordenamiento y comportamiento de conflictos
Sección titulada «Ordenamiento y comportamiento de conflictos»Ordenamiento a nivel de descubrimiento
Sección titulada «Ordenamiento a nivel de descubrimiento»Los proveedores de capacidades se ordenan por prioridad (mayor primero). La deduplicación es por clave de capacidad, gana el primero.
Para hooks, la clave de capacidad es ${type}:${tool}:${name}. Los duplicados sombreados de proveedores de menor prioridad se marcan y se excluyen de la lista descubierta efectiva.
Orden de carga
Sección titulada «Orden de carga»discoverAndLoadHooks construye una lista plana allPaths, deduplicada por ruta absoluta resuelta, luego loadHooks itera en ese orden.
El orden de los archivos dentro de cada directorio descubierto depende de la salida de readdir; el cargador de hooks no realiza una ordenación adicional.
Orden de manejadores en tiempo de ejecución
Sección titulada «Orden de manejadores en tiempo de ejecución»Dentro de HookRunner, el orden es determinista por secuencia de registro:
- orden del arreglo de hooks
- orden de registro de manejadores por hook/evento
Comportamiento de conflictos por tipo de evento:
tool_call: gana el último resultado retornado, a menos que un manejador bloquee; el primer bloqueo produce un cortocircuitotool_result: gana la última sobreescritura retornada (sin cortocircuito)context: encadenado; cada manejador recibe la salida de mensajes del manejador anteriorbefore_agent_start: se conserva el primer mensaje retornado; los mensajes posteriores se ignoransession_before_*: se rastrea el último resultado retornado;cancel: trueproduce un cortocircuito inmediatosession.compacting: gana el último resultado retornado
Conflictos de comandos/renderizadores:
getCommand(name)retorna la primera coincidencia entre hooks (gana el primero cargado)getMessageRenderer(customType)retorna la primera coincidenciagetRegisteredCommands()retorna todos los comandos (sin deduplicación)
Interacciones de UI (HookContext.ui)
Sección titulada «Interacciones de UI (HookContext.ui)»HookUIContext incluye:
select,confirm,input,editornotifysetStatuscustomsetEditorText,getEditorText- getter de
theme
ctx.hasUI indica si la UI interactiva está disponible.
Cuando se ejecuta sin UI, el comportamiento predeterminado del contexto sin operación es:
select/input/editorretornanundefinedconfirmretornafalsenotify,setStatus,setEditorTextson operaciones sin efectogetEditorTextretorna""
Comportamiento de la línea de estado
Sección titulada «Comportamiento de la línea de estado»El texto de estado del hook establecido mediante ctx.ui.setStatus(key, text):
- se almacena por clave
- se ordena por nombre de clave
- se sanea (
\r,\n,\t→ espacios; espacios repetidos colapsados) - se une y se trunca por ancho para la visualización
Propagación de errores y recuperación
Sección titulada «Propagación de errores y recuperación»En tiempo de carga
Sección titulada «En tiempo de carga»- módulo inválido o exportación por defecto faltante → capturado en
LoadHooksResult.errors - la carga continúa para otros hooks
En tiempo de evento
Sección titulada «En tiempo de evento»HookRunner.emit(...) captura los errores de los manejadores para la mayoría de los eventos y emite HookError a los escuchadores (hookPath, event, error), luego continúa.
emitToolCall(...) es más estricto: los errores de los manejadores no se absorben allí; se propagan al llamador. En HookToolWrapper, esto bloquea la llamada a la herramienta (a prueba de fallos).
Ejemplos de API realistas
Sección titulada «Ejemplos de API realistas»Bloquear comandos bash inseguros
Sección titulada «Bloquear comandos bash inseguros»import type { HookAPI } from "@f5-sales-demo/xcsh/hooks";
export default function (pi: HookAPI): void { pi.on("tool_call", async (event, ctx) => { if (event.toolName !== "bash") return; const cmd = String(event.input.command ?? ""); if (!cmd.includes("rm -rf")) return;
if (!ctx.hasUI) return { block: true, reason: "rm -rf blocked (no UI)" }; const ok = await ctx.ui.confirm("Dangerous command", `Allow: ${cmd}`); if (!ok) return { block: true, reason: "user denied command" }; });}Redactar la salida de la herramienta en post-ejecución
Sección titulada «Redactar la salida de la herramienta en post-ejecución»import type { HookAPI } from "@f5-sales-demo/xcsh/hooks";
export default function (pi: HookAPI): void { pi.on("tool_result", async event => { if (event.toolName !== "read" || event.isError) return;
const redacted = event.content.map(chunk => { if (chunk.type !== "text") return chunk; return { ...chunk, text: chunk.text.replaceAll(/API_KEY=\S+/g, "API_KEY=[REDACTED]") }; });
return { content: redacted }; });}Modificar el contexto del modelo por llamada LLM
Sección titulada «Modificar el contexto del modelo por llamada LLM»import type { HookAPI } from "@f5-sales-demo/xcsh/hooks";
export default function (pi: HookAPI): void { pi.on("context", async event => { const filtered = event.messages.filter(msg => !(msg.role === "custom" && msg.customType === "debug-only")); return { messages: filtered }; });}Registrar un comando slash con métodos de contexto seguros para comandos
Sección titulada «Registrar un comando slash con métodos de contexto seguros para comandos»import type { HookAPI } from "@f5-sales-demo/xcsh/hooks";
export default function (pi: HookAPI): void { pi.registerCommand("handoff", { description: "Create a new session with setup message", handler: async (_args, ctx) => { await ctx.waitForIdle(); await ctx.newSession({ parentSession: ctx.sessionManager.getSessionFile(), setup: async sm => { sm.appendMessage({ role: "user", content: [{ type: "text", text: "Continue from prior session summary." }], timestamp: Date.now(), }); }, }); }, });}Superficie de exportación
Sección titulada «Superficie de exportación»src/extensibility/hooks/index.ts exporta:
- APIs de carga (
discoverAndLoadHooks,loadHooks) - ejecutor y envoltorio (
HookRunner,HookToolWrapper) - todos los tipos de hooks
- reexportación de
execCommand
Y la raíz del paquete (src/index.ts) reexporta los tipos de hook como superficie de compatibilidad heredada.