Skip to content

CH 21 · Plugins Hook e Intercepción: Manipular Antes de la Ejecución de la Herramienta

Recuento de palabras~2,560 palabrasTiempo~22 minRequisitosCH 20 (defineTool)NivelReproducible

Objetivo del capítulo

En CH 20 dejamos que el modelo llamara a su propia herramienta. Este capítulo va más allá: cuelga tu propia lógica antes y después de que la herramienta se ejecute — registra quién llamó qué, intercepta herramientas que no deberían llamarse, decide permitir o denegar.

Esto son "plugins hook". Es la base del sistema de permisos, sandbox y capacidades de auditoría de dsh, y la encarnación más típica de "todo es un plugin".

Las llamadas a herramientas no son una línea recta

En CH 11 mencionamos un concepto: cuando el modelo dice llamar a una herramienta, no se ejecuta directamente, sino que pasa por un pipeline extensible. El equipo oficial lo convirtió en un "pipeline protegido" — cada etapa puede ser interceptada y mejorada por plugins.

Una llamada a herramienta pasa por estas etapas:

text
El modelo quiere llamar a una herramienta

pre-execute   → Compuerta de política: allow / deny / ask (permiso, sandbox, intercepción todo aquí)

guard         → Guardia monotónica: una vez denegada, listeners posteriores no pueden revocar (línea de defensa final)

execute       → Ejecuta realmente la herramienta

post-execute  → Transformación del resultado: reescribe el valor de retorno, añade contenido

result        → Observación de solo lectura: echa un vistazo al resultado, no puede cambiar

El resultado vuelve al modelo

Los "Hooks" son plugins montados en una etapa concreta: usa ctx.on('tools/xxx', ...) para suscribirte al evento correspondiente, y haz lo que quieras en el evento.

Los docs oficiales tienen una tabla clara sobre lo que puede hacer cada punto de extensión:

Punto de extensiónEfectoUso típico
tools/pre-executeCapa de decisión antes de la ejecución de la herramientaAllow / deny / ask, compuerta de permiso
ctx.tools.guard()Denegación final monotónicaLímite duro que no puede ser revocado por listeners posteriores
tools/executeEnvuelve todo el ciclo de dispatchAñadir timeout, reintento, recolección de métricas
tools/post-executeTransforma explícitamente el resultadoReemplazar contenido mostrado, añadir contexto visible para el modelo
tools/resultObservación de solo lectura del resultado inmutableLogs de auditoría, estadísticas, no puede cambiar

pre-execute es un evento waterfall: tu listener puede devolver next() (permitir) o { kind: 'deny', reason: '...' } (denegar).

Práctica: escribe un plugin Hook de "Auditoría + Intercepción"

Escribiremos un plugin hook que tanto registra cada llamada a herramienta como deniega herramientas especificadas. La lista de denegación se hace configurable — practicando al mismo tiempo la capacidad "el plugin puede configurarse".

Paso 1: crea directorio, instala dependencias

En un workspace donde quieras poner plugins:

powershell
New-Item -ItemType Directory -Path "hook-demo\src" -Force
cd "E:\software-workspace\DeepSeek harness demo\hook-demo"   # sustituye por tu directorio
npm init -y
npm install @deepseek-ai/schemastery

schemastery es la librería para definir schemas de configuración (cuando un plugin necesita ser configurable, usa esto para declarar la forma y los valores por defecto de la configuración). Recuerda añadir "type": "module" en package.json.

Paso 2: escribe el plugin Hook

Crea hook-demo\src\audit.js:

js
import Schema from '@deepseek-ai/schemastery'

export const name = 'audit-hook'

export const Config = Schema.object({
  denyTools: Schema.array(Schema.string()).default([]),
})

export function apply(ctx, config) {
  ctx.on('tools/pre-execute', (exec, next) => {
    console.log(`[audit] Tool will be called: ${exec.name}`)
    if (config.denyTools.includes(exec.name)) {
      return { kind: 'deny', reason: `Policy: this session is forbidden from calling ${exec.name}` }
    }
    return next()
  })
}

Desglose línea por línea:

  • Config: declara los elementos configurables del plugin. denyTools es un array de cadenas, por defecto un array vacío. El config en apply(ctx, config) es el resultado de fusionar la configuración del usuario y los valores por defecto.
  • ctx.on('tools/pre-execute', ...): suscríbete al evento antes de la ejecución de la herramienta. Cada vez que una herramienta esté a punto de ser llamada, pasa por aquí.
  • console.log: auditoría — log de que esta herramienta está a punto de ser llamada.
  • config.denyTools.includes(exec.name): si esta herramienta está en la lista de denegación, devuelve { kind: 'deny', reason } para interceptarla.
  • return next(): en caso contrario, permite, deja que el pipeline continúe.

Paso 3: pasa la configuración en cordis.yml

Crea hook-demo\cordis.yml (sustituye la ruta por la tuya, recuerda %20 para los espacios):

yaml
- insert:
    - id: audit
      name: 'file:///E:/tu-workspace/hook-demo/src/audit.js'
      config:
        denyTools: ['pwsh']

Aquí config añade pwsh (PowerShell) a la lista de denegación. Fíjate: el código del plugin no cambió ni un solo carácter, pero el comportamiento cambió — eso es exactamente para lo que sirve la configuración, y el principio de diseño oficial de "sin parámetros ajustables hardcodeados": los valores que se pueden cambiar en cordis.yml no deberían estar hardcodeados en el código.

Paso 4: ejecútalo y observa el efecto

Usa headless para ejecutar una vez, deja que el modelo llame a pwsh:

powershell
cd tu-directorio-de-workspace
dsh --profile headless --patch "./hook-demo/cordis.yml" "Use pwsh to run Get-ChildItem to list the current directory"

Mi salida real de terminal:

Dos logs [audit] amarillos son nuestro hook registrando: el modelo primero llamó a skill, luego a pwsh — cada llamada a herramienta pasó por nuestro hook. Y pwsh está en la lista de denegación, así que fue deny'd, el modelo percibió la denegación, reportó proactivamente "this session is forbidden from calling pwsh", y ofreció una alternativa.

Un plugin, haciendo tanto auditoría (visible) como intercepción (controlable). Ese es el poder de los hooks.

Deja que dsh lo haga: un prompt lo resuelve

Es más rápido que dsh escriba este plugin. Envía directamente en el cuadro de entrada de la Web UI:

text
In my current workspace, help me write a hook plugin: print a log line before a tool is called, and be able to deny specified tools via configuration. Implement per the official spec, run a headless to verify it can record and intercept, and finally tell me the result.

Irá a leer los docs oficiales por sí mismo, escribirá el plugin, verificará que tanto el registro como la intercepción funcionan. Tú solo verificas.

Este es el resultado de ejecutar yo realmente este prompt: primero expuso una versión de los puntos de implementación él mismo — los plugins solo pueden tener exports con nombre de name / inject / apply, inject: ['tools'] asegura que el registro de herramientas esté listo, usa ctx.on('tools/pre-execute', ...) para colgar el hook (consistente con el ejemplo oficial de compuerta de permisos), devuelve { kind: 'deny', reason } para denegar, await next() para permitir — incluso "la lista de denegación va por config, sin cambio de código" lo pensó por ti.

Este diagrama de trayectoria es su proceso de trabajo completo: write para escribir el archivo del plugin, pwsh para ejecutar su propio script de verificación (8 comprobaciones todas pasan), todo_write para actualizar la lista de tareas... cada uno es una llamada a herramienta.

Errores comunes

ProblemaQué está pasandoCómo manejarlo
Olvidaste return next()Evento waterfall sin next, el pipeline se atascapre-execute / post-execute deben devolver next() o una decisión
El modelo sigue reintentando tras denyEl modelo no sabe que esta herramienta no está permanentemente disponibleEscribe el reason claramente, el modelo lo verá y cambiará a un enfoque diferente
La configuración no surte efectoconfig en cordis.yml es incorrectoComprueba que el nombre del campo y el tipo del schema coincidan
Reporta Cannot find package '@deepseek-ai/schemastery'El directorio del plugin no instaló dependenciasnpm install @deepseek-ai/schemastery

Lo que aprendiste en este capítulo

  • [ ] Enuncias las principales etapas del pipeline de llamadas a herramienta (pre-execute / execute / post-execute / result)
  • [ ] Usas ctx.on('tools/pre-execute', ...) para escribir un plugin hook
  • [ ] Devuelves { kind: 'deny', reason } para interceptar una herramienta, next() para permitir
  • [ ] Usas Config + Schemastery para hacer configurable el plugin (lista de denegación)
  • [ ] Enuncias el principio de diseño de "sin parámetros ajustables hardcodeados"

Open Source · MIT · Community Driven