CH 18 · Seu Primeiro Plugin: hello-plugin
Objetivo do Capítulo
No CH 17 você aprendeu a instalar plugins feitos por outras pessoas. Este capítulo começa a escrever o seu próprio — o objetivo é o mínimo: escrever um hello-plugin e realmente tê-lo carregado pelo dsh. Você não escreve ferramentas, não toca na interface, você apenas verifica uma coisa: o plugin que você escreve pode ser descoberto, carregado e executado pelo dsh. Uma vez que este passo está conectado, as coisas legais do CH 19 ao CH 23 (ferramentas, hooks, UI, publicação) todas crescem em cima disso.
Primeiro, uma mentalidade: plugins não são misteriosos. O CH 08 disse "tudo é plugin", o reverso é: se você quer que o dsh tenha mais uma capacidade, escreva um pequeno módulo que exporta uma função apply. Esse é todo o esqueleto de um plugin.
Prática: Escreva um hello-plugin
No workspace que você quiser, crie o diretório (primeiro cd para esse diretório, então execute):
New-Item -ItemType Directory -Path "hello-plugin\src" -ForceEm seguida, crie hello-plugin\src\hello-plugin.js, escreva:
export const name = 'hello-plugin'
export function apply(ctx) {
console.log('[hello-plugin] plugin loaded!')
}Apenas essas duas linhas de lógica principal: quando o plugin é carregado, imprima [hello-plugin] plugin loaded!. Se conseguir imprimir, isso prova que "seu código foi executado pelo dsh" — esse é o primeiro marco.
Aqui usamos JS em vez do exemplo oficial TS: o dsh instalado globalmente não tem um runtime tsx embutido, e carregar
.tsdiretamente vai dar erro; usar.jsnão precisa de build, zero dependências, roda em cinco minutos. Quando escrevermos plugins mais complexos, traremos o TypeScript e a cadeia de build (CH 20 expande isso).
Carregue-o no dsh
Apenas ter o arquivo não basta, o dsh não sabe carregá-lo. Precisamos de um "overlay" para dizer ao dsh: carregue adicionalmente este plugin. Crie hello-plugin\cordis.yml (o caminho em name abaixo é um exemplo, substitua pelo seu próprio diretório, veja as notas depois):
- insert:
- id: hello
name: 'file:///E:/software-workspace/DeepSeek%20harness%20demo/hello-plugin/src/hello-plugin.js'Três notas:
namedeve ser uma URL completa começando comfile://, você não pode escreverE:\...ouE:/.... No Windows, o carregador de módulos do dsh só aceita o formatofile:///E:/...; escrever o caminho da letra de unidade diretamente vai reportarOnly URLs with a scheme in: file, data, and node are supported.- Espaços no caminho devem ser codificados como
%20. Por exemplo, se seu diretório émy work space, escreva comomy%20work%20space. - Este é um caminho absoluto. O arquivo de patch só contribui com configuração, a raiz de resolução do módulo ainda é o diretório do profile, então plugins locais devem ser escritos como caminhos completos.
Execute o headless Uma Vez para Verificação Rápida
Não toque na Web UI, use o headless para verificar rapidamente se o plugin está realmente carregado:
cd seu-diretório-de-workspace
dsh --profile headless --patch "./hello-plugin/cordis.yml" "Just reply: hi"Na saída você verá estas duas linhas:
[hello-plugin] plugin loaded!
hiA primeira linha é impressa pelo seu plugin na inicialização, a segunda é a resposta do modelo após completar a tarefa. Ver [hello-plugin] plugin loaded!, seu primeiro plugin está funcionando.
Então Carregue-o na Web UI
O headless pode verificar, mas o plugin deve ser usado na Web UI. Primeiro pare o dsh web em execução (caso contrário a porta está ocupada), então inicie com o patch:
dsh web --patch "./hello-plugin/cordis.yml"Abra http://127.0.0.1:3080, o terminal onde o dsh foi iniciado também vai imprimir [hello-plugin] plugin loaded!. O hello-plugin não tem efeito de UI por enquanto, sua única "saída" é essa linha de log — mas isso prova que ele entrou na árvore de plugins da web, junto com os membros da árvore que o CH 08 falou.

Olhe para a saída do terminal: a primeira linha é o log de carregamento do plugin impresso na inicialização do dsh, as próximas duas linhas são as informações de pronto da Web UI.
Limpeza Automática ao Descarregar
Tudo o que você registra com ctx (listeners de evento, ferramentas, timers) será limpo automaticamente pelo framework quando o plugin for descarregado; você não precisa manualmente fazer removeListener ou clearInterval. Se você tem recursos que precisam de liberação manual (por exemplo, uma conexão de rede), use ctx.effect() para dizer ao framework como limpar:
export function apply(ctx) {
ctx.effect(() => {
const timer = setInterval(() => {
console.log('heartbeat')
}, 5000)
// A função retornada executa quando o plugin é descarregado
return () => clearInterval(timer)
})
}A função de limpeza retornada por effect será chamada no momento em que o plugin for descarregado — esta é a forma padrão que o dsh ajuda você a gerenciar o ciclo de vida dos recursos.
Declare Dependências: inject
Se seu plugin precisa de outras capacidades (por exemplo, tools, llm), declare inject, e o framework vai garantir que as dependências estejam prontas antes de carregar seu plugin:
export const name = 'my-tool-plugin'
export const inject = ['tools']
export function apply(ctx) {
// Aqui ctx.tools está garantido para estar disponível
ctx.tools.register(/* ... */)
}inject é o ponto de entrada para "dependências de serviço" no Cordis. Vamos nos familiarizar com isso por agora, e usá-lo oficialmente no CH 20 quando escrevermos plugins de ferramenta.
Três Formas de Plugin
A função apply é a forma mais comum, mas plugins suportam três formas de escrita (CH 19 vai detalhar cada uma, aqui está a visão geral):
| Forma | Como se parece | Quando usar |
|---|---|---|
| Função | export function apply(ctx) {} | Escolha padrão, o que este tutorial usa |
| Objeto | export default { name, apply(ctx) {} } | Quando você quer carregar alguns metadados estáticos juntos |
| Classe | export default class extends Service {} | Quando você quer fornecer serviços para outros plugins (CH 19 expande) |
Por enquanto, apenas memorize uma frase: a forma de função resolve 90% das necessidades, deixe a forma de serviço para quando "seu plugin precisa ser dependido por outros plugins".
Deixe o dsh Fazer: Um Prompt Resolve
Os passos acima você executou manualmente, tudo aprendido. Mas o próprio dsh é um Agent — ele pode fazer o trabalho de escrever plugins, e "ele escreve e verifica sozinho".
Envie este prompt diretamente na caixa de entrada da Web UI:
In my current workspace, help me write a minimum hello-plugin plugin that dsh can load. First go read the official dsh plugin development docs, figure out how plugins should be written and loaded, then implement per the official spec, verify it's actually loaded, and finally tell me the result and where the files are.Você não precisa dizer a ele nenhum detalhe técnico — ele vai ler a documentação oficial de desenvolvimento de plugins sozinho, decidir como escrever, como carregar, como verificar. Você apenas observa ele trabalhar, então abre o arquivo para verificar o que ele escreveu. Esta é exatamente a extensão de "tudo é plugin": o trabalho de escrever plugins também pode ser feito por um Agent montado de plugins.

Eu realmente executei uma vez, o canto superior direito mostra os materiais de referência que ele listou e os arquivos produzidos (package.json, index.js, cordis.patch.yml). Depois que ele terminou de escrever, o painel de arquivos à direita mostra diretamente o novo diretório hello-plugin no workspace — exatamente onde o plugin de barra lateral instalado no CH 17 brilha, sem precisar trocar para o gerenciador de arquivos para verificar o que ele escreveu.
Armadilhas Comuns
| Problema | O que está acontecendo | Como lidar |
|---|---|---|
Reporta Only URLs with a scheme in: file... | No Windows o caminho é escrito como uma forma de letra de unidade | Mude name para uma URL completa como file:///E:/... |
| Reporta arquivo/módulo não encontrado | Espaços no caminho não estão codificados | Escreva espaços como %20 |
| Sem resposta após patch e reinicialização? | Caminho do plugin ou yml está com erro de digitação | Verifique a grafia de id e name, use dsh --profile web --patch ./hello-plugin/cordis.yml --dump-config para ver se o plugin está na árvore de configuração |
Carregar .ts diretamente dá erro? | O dsh global não tem um runtime tsx embutido | Primeiro use .js para rodar sem build, quando TS for necessário faça build para .js primeiro então carregue |
| Porta ocupada, não consegue iniciar? | dsh web anterior ainda está em execução | Pare o processo antigo primeiro, então inicie |
O que você aprendeu neste capítulo
Você passa se conseguir completar os itens abaixo:
- [ ] Indique a forma mínima de um plugin: um módulo que exporta uma função
apply(ctx) - [ ] Crie um hello-plugin e declare-o com uma URL
file://emcordis.yml - [ ] Use
dsh --profile headless --patch ...para verificar rapidamente se o plugin está carregado - [ ] Inicie a web com
--patchpara trazer o plugin para a árvore de plugins da Web UI - [ ] Saiba que
ctx.effect()faz limpeza de recursos,injectdeclara dependências de serviço, plugins têm três formas: função / objeto / classe
