Skip to content

CH 21 · Hook Plugins e Interceptação: Adulterando Antes da Execução da Ferramenta

Número de palavras~2.560 palavrasTempo~22 minPré-requisitosCH 20 (defineTool)NívelReproduzível

Objetivo do Capítulo

No CH 20 deixamos o modelo chamar sua própria ferramenta. Este capítulo vai além: pendure sua própria lógica antes e depois da execução da ferramenta — registre quem chamou o quê, intercepte ferramentas que não deveriam ser chamadas, decida permitir ou negar.

Este é "hook plugins". É a fundação do sistema de permissões do dsh, sandbox, capacidades de auditoria, e a manifestação mais típica de "tudo é plugin".

Chamadas de Ferramenta Não São uma Linha Reta

No CH 11 mencionamos um conceito: quando o modelo diz para chamar uma ferramenta, ela não executa diretamente, mas passa por um pipeline extensível. A equipe oficial tornou isso um "pipeline guardado" — cada estágio pode ser interceptado e aprimorado por plugins.

Uma chamada de ferramenta passa por estes estágios:

text
O modelo quer chamar uma ferramenta

pre-execute   → Portão de política: allow / deny / ask (permissão, sandbox, interceptação todos aqui)

guard         → Guarda monotônico: uma vez negado, listeners subsequentes não podem revogar (linha final de defesa)

execute       → Execute realmente a ferramenta

post-execute  → Transformação de resultado: reescreva valor de retorno, anexe conteúdo

result        → Observação somente leitura: dê uma olhada no resultado, não pode mudar

O resultado retorna ao modelo

"Hooks" são plugins montados em um certo estágio: use ctx.on('tools/xxx', ...) para se inscrever no evento correspondente, e faça o que você quer no evento.

A documentação oficial tem uma tabela clara sobre o que cada ponto de extensão pode fazer:

Ponto de extensãoEfeitoUso típico
tools/pre-executeCamada de decisão antes da execução da ferramentaAllow / deny / ask, portão de permissão
ctx.tools.guard()Negação final monotônicaLimite rígido que não pode ser revogado por listeners subsequentes
tools/executeEmbrulha todo o ciclo de despachoAdicione timeout, retry, coleta de métricas
tools/post-executeTransforma explicitamente o resultadoSubstitua conteúdo exibido, anexe contexto visível ao modelo
tools/resultObservação somente leitura de resultado imutávelLogs de auditoria, estatísticas, não pode mudar

pre-execute é um evento em cascata: seu listener pode retornar next() (permitir) ou { kind: 'deny', reason: '...' } (negar).

Prática: Escreva um Plugin Hook de "Auditoria + Interceptação"

Vamos escrever um plugin hook que tanto registra toda chamada de ferramenta quanto nega ferramentas especificadas. A lista de negação é tornada configurável — praticando a capacidade de "plugin pode ser configurado" ao mesmo tempo.

Passo 1: Crie Diretório, Instale Dependências

Em um workspace onde você quer colocar plugins:

powershell
New-Item -ItemType Directory -Path "hook-demo\src" -Force
cd "E:\software-workspace\DeepSeek harness demo\hook-demo"   # substitua pelo seu diretório
npm init -y
npm install @deepseek-ai/schemastery

schemastery é a biblioteca para definir schemas de configuração (quando um plugin precisa ser configurável, usa isso para declarar a forma e padrões da configuração). Lembre-se de adicionar "type": "module" em package.json.

Passo 2: Escreva o Plugin Hook

Crie 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()
  })
}

Detalhamento linha por linha:

  • Config: declara os itens configuráveis do plugin. denyTools é um array de strings, com padrão de array vazio. O config em apply(ctx, config) é o resultado de mesclar a configuração do usuário e os padrões.
  • ctx.on('tools/pre-execute', ...): inscreva-se no evento antes-da-execução-da-ferramenta. Toda vez que uma ferramenta está prestes a ser chamada, passa por aqui.
  • console.log: auditoria — registre que esta ferramenta está prestes a ser chamada.
  • config.denyTools.includes(exec.name): se esta ferramenta está na lista de negação, retorne { kind: 'deny', reason } para interceptá-la.
  • return next(): caso contrário permita, deixe o pipeline continuar.

Passo 3: Passe Config em cordis.yml

Crie hook-demo\cordis.yml (substitua o caminho pelo seu, lembre-se de %20 para espaços):

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

Aqui config adiciona pwsh (PowerShell) à lista de negação. Note: o código do plugin não mudou um único caractere, mas o comportamento mudou — é exatamente para isso que serve a configuração, e o princípio oficial de design de "sem parâmetros ajustáveis hard-coded": valores que podem ser alterados em cordis.yml não devem ser hardcoded no código.

Passo 4: Execute e Veja o Efeito

Use o headless para executar uma vez, deixe o modelo chamar pwsh:

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

Minha saída real do terminal:

Dois logs amarelos [audit] são nosso hook registrando: o modelo primeiro chamou skill, então pwsh — toda chamada de ferramenta passou pelo nosso hook. E pwsh está na lista de negação, então foi deny'd, o modelo percebeu a negação, proativamente reportou "this session is forbidden from calling pwsh", e ofereceu uma alternativa.

Um plugin, fazendo tanto auditoria (visível) quanto interceptação (controlável). Esse é o poder dos hooks.

Deixe o dsh Fazer: Um Prompt Resolve

É mais rápido fazer o dsh escrever este plugin. Envie diretamente na caixa de entrada da 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.

Ele vai ler a documentação oficial sozinho, escrever o plugin, verificar se tanto registro quanto interceptação funcionam. Você apenas verifica.

Este é o resultado de eu realmente executar este prompt: ele primeiro apresentou uma versão dos pontos de implementação sozinho — plugins só podem ter exportações nomeadas de name / inject / apply, inject: ['tools'] garante que o registro de ferramentas está pronto, use ctx.on('tools/pre-execute', ...) para pendurar o hook (consistente com o exemplo oficial de portão de permissão), retorne { kind: 'deny', reason } para negar, await next() para permitir — até "lista de negação vai pela config, sem mudança de código" foi pensado para você.

Este diagrama de trajetória é seu processo de trabalho completo: write para escrever o arquivo do plugin, pwsh para executar seu próprio script de verificação (8 verificações todas passam), todo_write para atualizar a lista de tarefas... cada um é uma chamada de ferramenta.

Armadilhas Comuns

ProblemaO que está acontecendoComo lidar
Esqueceu return next()Evento em cascata sem next, o pipeline travapre-execute / post-execute devem retornar next() ou uma decisão
Modelo continua tentando após denyModelo não sabe que esta ferramenta está permanentemente indisponívelEscreva o reason claramente, o modelo vai ver e mudar para uma abordagem diferente
Config não teve efeitoconfig em cordis.yml está erradoVerifique se o nome do campo e tipo do schema combinam
Reporta Cannot find package '@deepseek-ai/schemastery'Diretório do plugin não instalou dependênciasnpm install @deepseek-ai/schemastery

O que você aprendeu neste capítulo

  • [ ] Indique os principais estágios do pipeline de chamada de ferramenta (pre-execute / execute / post-execute / result)
  • [ ] Use ctx.on('tools/pre-execute', ...) para escrever um plugin hook
  • [ ] Retorne { kind: 'deny', reason } para interceptar uma ferramenta, next() para permitir
  • [ ] Use Config + Schemastery para tornar o plugin configurável (lista de negação)
  • [ ] Indique o princípio de design de "sem parâmetros ajustáveis hard-coded"

Open Source · MIT · Community Driven