CH 21 · Hook Plugins e Interceptação: Adulterando Antes da Execução da Ferramenta
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:
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ão | Efeito | Uso típico |
|---|---|---|
tools/pre-execute | Camada de decisão antes da execução da ferramenta | Allow / deny / ask, portão de permissão |
ctx.tools.guard() | Negação final monotônica | Limite rígido que não pode ser revogado por listeners subsequentes |
tools/execute | Embrulha todo o ciclo de despacho | Adicione timeout, retry, coleta de métricas |
tools/post-execute | Transforma explicitamente o resultado | Substitua conteúdo exibido, anexe contexto visível ao modelo |
tools/result | Observação somente leitura de resultado imutável | Logs 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:
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/schemasteryschemastery é 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:
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. Oconfigemapply(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):
- 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:
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:
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
| Problema | O que está acontecendo | Como lidar |
|---|---|---|
Esqueceu return next() | Evento em cascata sem next, o pipeline trava | pre-execute / post-execute devem retornar next() ou uma decisão |
| Modelo continua tentando após deny | Modelo não sabe que esta ferramenta está permanentemente indisponível | Escreva o reason claramente, o modelo vai ver e mudar para uma abordagem diferente |
| Config não teve efeito | config em cordis.yml está errado | Verifique 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ências | npm 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"
