green-dalii/dsh-plugin-dev-skill

Um pacote de habilidades que capacita qualquer agente a desenvolver plugins DeepSeek Harness (DSH) de forma correta, eficiente e de acordo com as convenções oficiais.

O que é dsh-plugin-dev-skill?

Esta habilidade é um guia operacional completo para o desenvolvimento de plugins no ecossistema DeepSeek Harness (DSH), construído sobre o framework Cordis. Ela fornece um modelo mental, regras rígidas, templates de código prontos para uso, um fluxo de desenvolvimento passo a passo e uma lista de verificação final. A habilidade cobre todos os tipos de plugins (ferramentas, adaptadores LLM, provedores de serviço, hooks, UI, pontes de protocolo) e é essencial para qualquer tarefa que envolva a escrita, modificação ou depuração de plugins DSH, configuração de arquivos como `cordis.yml` e `dsh.profile`, ou a compreensão dos serviços e eventos internos do DSH. O conteúdo é baseado em documentação extraída dos diretórios `References/` do projeto.

Funciona com✓Claude Code✓Codex CLI~Cursor
npx skills add green-dalii/dsh-plugin-dev-skill

Installed? Explore more Produtividade e Colaboração skills: steipete/gemini, steipete/gh-issues, steipete/skill-creator · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Habilidade de Desenvolvimento de Plugin DeepSeek Harness

Permite que qualquer Agente desenvolva plugins DeepSeek Harness (DSH) de forma correta, eficiente e em conformidade com as convenções oficiais. Esta habilidade é o manual de operações para o desenvolvimento de plugins DSH: primeiro fornece o modelo mental e as regras rígidas, depois oferece templates de código que podem ser copiados diretamente e um fluxo passo a passo, e, finalmente, uma lista de verificação. Para contexto aprofundado, veja os documentos resumidos no diretório References/.


0. Quando Usar Esta Habilidade

Esta habilidade deve ser carregada quando a tarefa envolve qualquer um dos seguintes:

  • Escrever, modificar ou depurar plugins (tool, adaptador LLM, provedor de serviço, hooks, UI, ponte de protocolo) para o DeepSeek Harness
  • Escrever ou modificar as configurações cordis.yml / cordis.patch.yml / dsh.profile / dsh.bundle
  • Usar dsh plugin ... para instalar, remover ou empacotar plugins
  • Entender os serviços ctx.tools, ctx.llm, ctx.agents, ctx.sessions ou eventos como tools/*, agent/*, session/event no DSH
  • Estudar como o modelo de plugins do DSH (framework Cordis) funciona

Leia todo o SKILL.md antes de começar a trabalhar. Antes de começar, leia os arquivos cordis.yml / profile / código-fonte relevantes existentes no projeto para confirmar as convenções atuais.


1. Modelo Mental: DSH é um Sistema Plug-in Construído Sobre o Cordis

Em resumo: DeepSeek Harness é um SDK Agent Harness onde cada capacidade – ferramentas, adaptadores LLM, acesso a arquivos, o próprio loop do agente – é um plugin (componente), montado em um contexto compartilhado.

Três conceitos principais (do artigo "A Programming Paradigm for Spatiotemporal Composability", veja References/10-spatiotemporal.md):

ConceitoSignificadoManifestação no Código
Efeito Reversível (revertible effect)Cada modificação do componente no ambiente compartilhado carrega uma operação inversa; o runtime rastreia e restaura na ordem LIFO quando o componente é descarregadoctx.effect(() => () => cleanup); ctx.on(), ctx.tools.register() etc. são efeitos, descarregamento desfaz automaticamente
Coefeito Reativo (reactive coeffect)O componente declara de quais serviços depende; quando os serviços aparecem/desaparecem/trocam de implementação, o componente ativa/desativa/recarrega automaticamenteexport const inject = ['tools']; quando a dependência desaparece, o plugin faz dispose automaticamente; quando restaurada, recarrega automaticamente
Fiber (Instância de Componente)Cada instância de plugin carregada é um fiber, com uma máquina de estados de ciclo de vidaPENDING → LOADING → ACTIVE → UNLOADING → DISPOSED (se apply lançar erro, fica FAILED)

Regras de Ferro resultantes:

  1. Todo registro deve ser feito através de ctx (ctx.on / ctx.tools.register / ctx.effect / ctx.plugin / ctx.provide). Tudo que é registrado via ctx é limpo automaticamente no descarregamento. Nunca use removeListener / clearInterval manualmente.
  2. Qualquer recurso não gerenciado por ctx (conexões de rede, timers próprios, file handles, watchers) deve ser envolvido em ctx.effect(() => { ...; return () => cleanup }).
  3. Dependências devem ser declaradas (com inject). Não use ctx.get() para sondar dependências obrigatórias. Não presuma a ordem de carregamento – a ordem é determinada pelas dependências, não pela ordem das linhas em cordis.yml.
  4. A configuração deve ser configurável: qualquer parâmetro que possa ter valores diferentes em diferentes implantações deve ser um campo Config. Sem valores codificados (teste: é possível alterar esse valor no cordis.yml sem modificar o código?).
  5. Erros de configuração devem ser claros: use schema Schemastery para validar no carregamento. Configurações inválidas devem causar falha no carregamento com uma mensagem de erro explícita. Nunca opere com estado doente.
  6. Callbacks devem ser reproduzíveis/recarregáveis a quente: callbacks de apresentação (output.render, presentCall, presentResult) devem ser funções puras. Plugins podem ser descarregados e recarregados via HMR a qualquer momento; não dependa de estado global que persista entre recargas.

2. Ambiente e Ferramentas

  • Os plugins são módulos TypeScript (JS também é aceito), executados em ambiente Node.js ESM.
  • Pacotes de dependência principais (todos do escopo @deepseek-ai):
    • @deepseek-ai/cordis — Framework de plugins (context, fiber, eventos, registry)
    • @deepseek-ai/schemastery — Validação de schema de configuração (implementação Standard Schema)
    • @deepseek-ai/dsh-tools — defineTool e serviço de registro de ferramentas
    • @deepseek-ai/dsh-llm — LlmAdapter, StreamChunk, GenerateOptions, CallId
    • Pacotes de camada de capacidade: dsh-shell, dsh-session, dsh-agent, dsh-system-prompt, dsh-fs, dsh-jobs, dsh-credentials etc. (lista completa em References/08-capability-layering.md)
  • Dois entry points de desenvolvimento (escolha):
    • Checkout do código-fonte (recomendado para tutoriais): clone deepseek-ai/deepseek-harness, pnpm install, inicie a Web UI com pnpm dsh web --patch <seu-cordis.yml>, execute exemplos de tutorial Cordis puro com node --import tsx ../../vendor/cordis/bin.js.
    • CLI instalado: dsh --profile <nome> ... para iniciar um profile, dsh plugin --profile <nome> <args pnpm> para gerenciar plugins.

3. Forma Mínima de um Plugin (Aprenda Primeiro)

Um plugin é um módulo que exporta uma função apply:

import type { Context } from '@deepseek-ai/cordis'

export const name = 'my-plugin'   // Opcional, usado apenas para exibição de diagnóstico

export function apply(ctx: Context) {
  // Registre capacidades aqui
}

Montado via cordis.yml ou patch:

- name: './src/my-plugin.ts'        # Caminho relativo (código-fonte/desenvolvimento) ou nome do pacote npm (após instalação)
  id: my-plugin                     # Identificador estável; o loader usa o id para conciliar configurações

Três Formas de Plugin

  1. Forma de Função (recomendada por padrão):
    export const name = 'greet-tool'
    export const inject = ['tools']
    export function apply(ctx: Context) { /* ... */ }
    
  2. Forma de Objeto: export default { name, inject, apply(ctx) {} }
  3. Forma de Classe (quando precisa fornecer um serviço): export default class MyService extends Service { constructor(ctx) { super(ctx, 'myService') } }

Use a forma de função até que seja necessário fornecer um serviço.


4. Desenvolvendo uma Ferramenta (Tarefa Mais Comum)

As ferramentas são registradas em ctx.tools, definidas com defineTool. Template (pode copiar diretamente):

import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'

export const name = 'my-tool'
export const inject = ['tools']

export function apply(ctx: Context) {
  ctx.tools.register(defineTool({
    name: 'my_tool',                                  // Nome da ferramenta visto pelo modelo (convenção snake_case)
    description: 'Uma frase sobre o que ela faz.',    // Descrição funcional vista pelo modelo; deve ser específica e dizer quando usar
    parameters: {
      path: { type: 'string', required: true, description: 'O que o parâmetro significa' },
      limit: { type: 'number' },                      // Sem 'required' significa opcional
    },
    output: {
      schema: { type: 'string' },                     // Declara o valor JSON canônico retornado por execute
      render: (_args, value) => [{ type: 'text', text: value }],  // Converte para conteúdo visível ao modelo
    },
    async execute(args, exec) {
      // args já foi validado pelo schema e os tipos foram inferidos
      // exec.signal é o sinal de cancelamento; tarefas longas devem responder a ele
      return `resultado para ${args.path}`
    },
  }))
}

Contrato Completo do defineTool (Deve Ser Seguido)

  • parameters: Mapeamento do schema de parâmetros; a raiz é um objeto implicitamente aberto; cada propriedade pode ter required: true. Suporta string/number/integer/boolean/null/array/object/oneOf; nós de objeto explícito devem declarar additionalProperties: true | false.
  • output.schema: Declara o valor JSON canônico retornado por execute (objeto/array/escalar/null são aceitos). Esta é a API para chamadores programáticos (o await tools.xxx() do Code Mode), projetada para retornar diretamente handles e campos; a explicação para humanos vai em output.render.
  • execute(args, exec):
    • args já foram validados em runtime, mas restrições que o schema DSL não consegue expressar (string não vazia, número positivo, campos interdependentes) devem ser verificadas manualmente com throw new Error(...).
    • Retorna o valor canônico declarado em output.schema. Não retorne blocos de conteúdo, não force o chamador a fazer parse de ID a partir de linguagem natural.
    • Respeite exec.signal: cancele o trabalho em andamento quando o sinal for disparado.
    • Falhas de infraestrutura (exceção/valor de retorno inválido) → resultado marcado como isError. Sucesso com resultado não ideal (ex.: código de saída não-zero) → ainda retorne o valor canônico, explicando no render.
  • output.render(args, value): Função pura que converte o valor canônico em ContentBlock[] (texto visto pelo modelo). Nunca faça I/O ou leia estado da sessão aqui.
  • Opcional output.presentationMeta(args, value): Deriva metadados de UI reproduzíveis a partir do valor canônico (função pura).
  • Opcional presentCall(args) / presentResult(args, result): Declara a intenção de renderização do cartão da ferramenta na UI (card: 'generic' | 'terminal' | 'diff' | 'search' | 'web' | 'read'). Deve ser função pura (executada em stream ao vivo e reprodução de sessão), sem I/O.
  • Não coloque metadados de runtime como timeoutMs, isConcurrencySafe, finalizeContent no schema visível ao modelo – eles não vazam para o modelo.
  • Parâmetros são imutáveis: trate args como somente leitura; após o registro, não altere o schema ou substitua os callbacks.

Tarefas de Longa Duração (Tarefas em Segundo Plano)

Para suportar run_in_background:

const jobs = ctx.get('jobs')
if (!jobs) throw new Error('trabalhos em segundo plano indisponíveis: carregue @deepseek-ai/dsh-jobs')
return {
  kind: 'background',
  jobId: jobs.start({
    kind: 'my-work',
    label: args.command,
    ...exec.agent ? { owner: exec.agent } : {},
    run: () => { /* retorna { cancel, done, readOutput? } */ },
  }),
}

Pontos-chave: após publicar o jobId, use o sinal de cancelamento da própria tarefa (gerenciado por job_kill / owner dispose), não use mais exec.signal; o schema de saída deve ser capaz de suportar { kind: 'background', jobId } como handle canônico. Consulte References/03-tools.md e a implementação @deepseek-ai/dsh-tool-bash.

Estratégias de Execução de Ferramentas (Pontos de Extensão, Use Conforme Necessário)

Ponto de ExtensãoModoUso
tools/pre-executewaterfallEstratégias de permitir/negar/perguntar (controle de acesso); retorna {kind:'allow'} / {kind:'deny',reason} / {kind:'ask'}
ctx.tools.guard()Guarda monotônicaNegação final; listeners subsequentes não podem desfazer (retorna reason = negação)
tools/executewaterfallEncapsular distribuição: timeout/retry/métricas; pode substituir exec.signal mas não pode remover
tools/post-executewaterfallSubstituir conteúdo/valor, impedir resultado, anexar contexto adicional ao modelo
tools/resultemitObservação somente leitura do resultado final imutável (logs, auditoria)

Não embute estratégias dentro da ferramenta; coloque estratégias de implantação em plugins de hook (plugins Cordis comuns que escutam esses eventos; veja exemplo de controle de acesso em References/11-cookbook.md).


5. Configuração do Plugin (Schemastery)

O plugin exporta Config (interface TS de mesmo nome + schema Schemastery); o Cordis valida e preenche valores padrão antes de apply:

import type { Context } from '@deepseek-ai/cordis'
import Schema from '@deepseek-ai/schemastery'

export interface Config {
  greeting: string
  maxRetries: number
  verbose?: boolean
}

export const Config: Schema<Config> = Schema.object({
  greeting: Schema.string().default('Hello'),
  maxRetries: Schema.number().default(3),
  verbose: Schema.boolean().default(false),
})

export function apply(ctx: Context, config: Config) {
  // config é sempre completo e validado
}

Passando configuração no cordis.yml:

- name: './src/my-plugin.ts'
  config:
    greeting: 'Olá'
    maxRetries: 5

Pontos-chave:

  • Config deve ser um objeto Standard Schema (construído com Schemastery), não um objeto comum exportado.
  • Construtores comuns: Schema.string().required() / .default(x), Schema.number(), Schema.boolean(), Schema.array(String), Schema.union(['a','b']), Schema.object({...}) aninhado.
  • Suporta expressões !!js para avaliação no carregamento (ex.: greeting: !!js process.env.GREETING ?? 'Hello'); !!js é válido apenas nos campos config e disabled.
  • Mudanças de configuração disparam HMR: instância antiga é descarregada (registros limpos automaticamente) → nova instância carregada. Não armazene configuração em cache fora do plugin.

6. Serviços e Dependências (Desenvolvimento do Lado do Servidor)

Usando um Serviço (Consumidor)

export const inject = ['tools']        // Dependência obrigatória: não carrega se não for atendida
export function apply(ctx: Context) {
  ctx.tools.register(/* ... */)        // Garantido que está pronto quando apply é chamado
}

Dependência opcional: não use inject, use ctx.get('service') para sondar (retorna undefined se não houver provedor).

Fornecendo um Serviço (Provedor)

Use uma subclasse de Service (plugin na forma de classe):

import { Service, type Context } from '@deepseek-ai/cordis'

declare module '@deepseek-ai/cordis' {
  interface Context {
    metrics: MetricsService     // Declaração de merge: dá tipo a ctx.metrics
  }
}

export default class MetricsService extends Service {
  static inject = ['llm']       // Serviços também podem depender de outros serviços
  constructor(ctx: Context) {
    super(ctx, 'metrics')       // 'metrics' é o nome do serviço
  }
  record(event: string, value: number) { /* ... */ }
}

export const name = 'metrics'
export function apply(ctx: Context) {
  ctx.plugin(MetricsService)
}

APIs de baixo nível (não comuns, mas é bom saber): ctx.provide(name, value) registra uma implementação de serviço (effect, desfaz no descarregamento), ctx.set/get para ler/escrever storage, ctx.accessor para definir propriedades computadas, ctx.mixin para pendurar membros do serviço no ctx.

Semântica do Comportamento do Serviço (Entender, Não Decorar)

  • inject não é uma verificação única de inicialização: se um serviço obrigatório desaparecer durante a execução → o plugin dependente faz dispose automaticamente; quando o serviço é restaurado → recarrega automaticamente. Isso garante que o consumidor nunca mantenha uma referência a um serviço descarregado.
  • Nomes de serviço são espaço de nomes global plano: tools, llm etc. já estão ocupados; nomes próprios devem ter um prefixo identificável (ex.: myCap, metrics).
  • Isolamento de serviços: em cordis.yml, use group + isolate para que diferentes grupos de plugins vejam instâncias diferentes do mesmo nome de serviço (ex.: dois grupos cada um com seu próprio provedor shell com timeout diferente).

7. Sistema de Eventos (Comunicação Entre Plugins)

Modos de Distribuição (Parte do Nome do Evento, Deve Ser Escolhido Corretamente)

ModoChamadaSemântica
emitctx.emit(name, ...args)Broadcast síncrono, não espera, não coleta retornos
parallelawait ctx.parallel(...)Todos os listeners executam concorrentemente e aguarda
serialawait ctx.serial(...)Executa sequencialmente, o primeiro retorno não-null/false/undefined vence e para
bailctx.bail(...)Versão síncrona do serial
waterfallawait ctx.waterfall(name, ...args, next)Middleware de encapsulamento; o listener recebe next, encapsula o valor de retorno downstream

Regra de ferro do waterfall: listeners que apenas observam/anotam DEVEM chamar next(); não chamar next() e retornar diretamente = curto-circuito intencional (negação/interceptação). Esquecer de chamar next() engole silenciosamente o comportamento padrão downstream.

Eventos Type-Safe

declare module '@deepseek-ai/cordis' {
  interface Events {
    'my-plugin/ready': (payload: { id: string }) => void
    'my-plugin/check': (input: string) => boolean | undefined
  }
}

Convenção de nomenclatura de eventos: namespace/action (ex.: agent/step, tools/result, session/event). Listeners registrados com ctx.on() são automaticamente limpos quando o plugin é descarregado.

Eventos Comuns do Harness (Usados Frequentemente no Desenvolvimento)

  • tools/pre-execute / tools/execute / tools/post-execute / tools/result — pipeline de ferramentas (veja tabela acima)
  • agent/* (agent/request, agent/pre-step, agent/session-start, agent/turn-stopping etc.) — ciclo de vida do agente e construção de requisições
  • session/event — fluxo de eventos de sessão persistido (turn/*, step/*, tool/call, tool/result, assistant/chunk são todos event.type aqui, não eventos Cordis de mesmo nome)
  • approval/request — waterfall de aprovação
  • system-prompt/assemble — montagem geral do prompt do sistema (retorno autoritativo; listeners têm a responsabilidade de preservar contribuições existentes)

Assinaturas completas/modos de disparo estão nos blocos cordis-surface gerados pelas páginas de cada subsistema (consulte References/08-capability-layering.md para orientação).


8. Adaptador LLM (Conectando Novos Provedores de Modelo)

Adaptador = classe que herda LlmAdapter e implementa stream(), convertendo requisições independentes de provedor do Harness em chamadas de API específicas e de volta para pedaços StreamChunk.

import type { Context } from '@deepseek-ai/cordis'
import Schema from '@deepseek-ai/schemastery'
import { LlmAdapter, type GenerateOptions, type StreamChunk } from '@deepseek-ai/dsh-llm'

class MyAdapter extends LlmAdapter {
  async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
    // 1. options.messages → formato do provedor
    // 2. Chame API streaming (deve passar options.signal)
    // 3. Resposta → sequência de StreamChunk
  }
}

export const name = 'my-llm-adapter'
export const inject = ['llm']
export const Config = Schema.object({
  apiKey: Schema.string().required(),
  providers: Schema.array(String).required(),
})

export function apply(ctx: Context, config: Config) {
  ctx.llm.registerAdapter(config.providers, new MyAdapter(config.apiKey))
}

Protocolo StreamChunk (Seguir Rigorosamente)

async function* chunks(): AsyncIterable<StreamChunk> {
  yield { type: 'block-start', index: 0, blockType: 'text' }
  yield { type: 'text-delta', index: 0, text: 'Hello' }
  yield { type: 'block-end', index: 0, block: { type: 'text', text: 'Hello world' } }
  // Bloco de chamada de ferramenta:
  yield { type: 'block-start', index: 1, blockType: 'tool-call' }
  yield { type: 'tool-call-delta', index: 1, id: CallId('c1'), name: 'bash', argumentsDelta: '{"command":"ls"}' }
  yield { type: 'block-end', index: 1, block: { type: 'tool-call', id: CallId('c1'), name: 'bash', arguments: '{"command":"ls"}' } }
  yield { type: 'usage', usage: { inputTokens: 100, outputTokens: 50 } }   // Deve ser antes de finish
  yield { type: 'finish', reason: { kind: 'stop' } }                        // Deve ser o último
}

Regras: cada block-start deve ter seu correspondente block-end; index incrementa a partir de 0; arguments de tool-call é sempre string JSON bruta; finish é o último bloco; usage antes de finish.

Erros e Metadados

  • Falhas de transporte/protocolo: throw new LlmError(msg, 'STABLE_CODE') (com código estável), não dependa da conversão automática de um Error comum.
  • Provedor não suporta um campo de GenerateOptions: throw new LlmError(..., 'UNSUPPORTED'), nunca ignore silenciosamente.
  • Cada requisição HTTP deve mesclar attributionHeaders() e passar options.signal.
  • Opcional: sobrescrever resolveModel() (retorna identidade do provedor/modelo + metadados opcionais de context/reasoning) e listModels() (publica opções de modelo).

Tutorial completo em References/05-llm-adapter.md; implementações de referência: packages/llm/llm-deepseek (SSE compatível com OpenAI) e packages/llm/llm-pi-ai.


9. Camadas de Capacidade (Três Papéis, Padrão de Design para Capacidades Substituíveis)

Quando uma capacidade precisa de provedores substituíveis (ex.: execução Bash), divida em três papéis, colocando em pacotes diferentes:

  1. Definição do Serviço (ex.: dsh-shell): Define a interface do serviço Cordis + tipos Request/Result. abstract class MyCapService extends Service { super(ctx,'myCap'); abstract execute(req): Promise<res> }
  2. Provedor do Serviço (ex.: dsh-bash-local): Implementa a interface. class MyCapLocal extends MyCapService { ... }; apply(ctx){ ctx.plugin(MyCapLocal) }
  3. Consumidor (ex.: dsh-tool-bash): Expõe a capacidade como ferramenta do modelo. inject: ['tools','myCap']

Direção da dependência: Provedor depende da Definição; Consumidor depende da Definição; Provedor e Consumidor não dependem um do outro. Trocar de provedor requer apenas alterar uma linha em cordis.yml; Definição e Consumidor permanecem inalterados.

Não faça divisão preventiva: só divida em pacotes se os papéis precisarem evoluir independentemente. Para plugins de ferramenta simples, um pacote é suficiente.


10. Empacotamento e Instalação (Entregando o Plugin ao Usuário)

Dois Conceitos

  • Bundle (pacote combinado): um pacote npm com uma camada de configuração anexa, cujo manifest declara dsh.bundle. É o que você escreve e distribui.
  • Profile: localizado em $DSH_HOME/profiles/<nome>, é um bundle inicializável, cujo manifest declara dsh.profile (contendo uma lista ordenada de bundles). É o que o usuário inicia.

Empacotando um Bundle

hello-plugin/
├── package.json       # Declara dsh.bundle
├── cordis.patch.yml   # Conteúdo da camada
└── index.js           # Módulo do plugin
// package.json
{
  "name": "dsh-hello-plugin",
  "version": "0.1.0",
  "type": "module",
  "main": "index.js",
  "files": ["index.js", "cordis.patch.yml"],
  "dsh": { "bundle": { "patch": "cordis.patch.yml" } }
}
# cordis.patch.yml
plugins:
  - name: dsh-hello-plugin
    config:
      greeting: Olá do DSH!

Distribua via npm: npm publish. O usuário instala com dsh plugin install dsh-hello-plugin --profile <nome>.

Camadas (Layers)

  • cordis.yml = base + bundles + patches do usuário.
  • Um bundle contribui com uma camada através de dsh.bundle.patch.
  • Múltiplas camadas são mescladas: as camadas posteriores sobrescrevem as anteriores.
  • Perfil: a base é $DSH_HOME/profiles/<nome>/cordis.yml; cada bundle declarado em dsh.profile.bundles empilha sua camada por cima.

Marcando um Plugin como Não Removível ou Padrão

Em cordis.yml:

- name: '@deepseek-ai/dsh-web'
  disabled: !!js process.env.DSH_NO_WEB  # false = habilitado, true/falsy = desabilitado

O campo disabled aceita !!js. Use com moderação.


11. HMR (Hot Module Replacement) — O que o Plugin Deve Saber

  • Estado deve ser recriável: não presuma que uma variável global sobrevive a recargas. Tudo que precisa persistir entre recargas deve usar ctx.effect com cleanup que restaura o estado anterior.
  • Hooks de ciclo de vida: além de apply, o plugin pode exportar dispose() (chamado quando instância antiga é descarregada antes da recarga). Raramente necessário se você usa ctx.effect.
  • Cuidado com timers: setInterval sem ctx.effect sobrevive ao descarregamento e causa vazamentos e comportamento indefinido. Sempre:
ctx.effect(() => {
  const timer = setInterval(cb, 1000)
  return () => clearInterval(timer)
})

12. Depuração de Um Plugin

  • Logs de diagnóstico: Use ctx.logger('my-plugin').info('mensagem'); o nível de log é controlado por DSH_LOG_LEVEL.
  • Inspeção de registros: ctx.tools.list() lista ferramentas registradas; ctx.get('llm') recupera o adaptador LLM.
  • Teste com um perfil mínimo: crie debug.yml com apenas as dependências essenciais, iniciando com pnpm dsh web --patch debug.yml.
  • Ferramenta de rastreamento de estado Cordis: veja References/07-debugging.md para ferramentas de linha de comando e truques do navegador.

A. Apêndice: Lista de Verificação de Desenvolvimento de Plugin

Antes de declarar o plugin pronto, verifique:

  • Uso correto de ctx: todo registro de evento, ferramenta, serviço ou recurso foi feito através de ctx (.on(), .register(), .effect(), .plugin(), .provide())? Nenhum removeListener manual?
  • Dependências declaradas: todos os serviços obrigatórios estão em inject? Não há sondagem (ctx.get) para dependências obrigatórias?
  • Configuração externa: parâmetros variáveis são configuráveis via Config? Nenhum valor codificado?
  • Obedece a semântica pura de render/present: output.render, presentCall, presentResult são funções puras? Sem I/O, sem leitura de estado da sessão?
  • Sinalização correta: tarefas longas respondem a exec.signal? Tarefas em segundo plano usam o próprio sinal de cancelamento?
  • Efeitos encapsulados: recursos não gerenciados (timers, conexões) estão dentro de ctx.effect?
  • Schema de saída útil: output.schema declara um formato programático? Ele não é apenas { type: 'string' } se a saída real é natural language?
  • Nome da ferramenta único: o nome da ferramenta (snake_case) está dentro do namespace do plugin? Ele não entra em conflito com ferramentas existentes?
  • Tratamento de erros: todos os caminhos de erro são tratados? Falhas de infraestrutura viram isError; resultados inesperados são explicados no render?
  • Convenções de nomes de eventos: os eventos usam a convenção namespace/action?
  • Isolamento de configuração: o plugin pode ser carregado várias vezes em contextos diferentes sem conflitos?

B. Apêndice: Referências

Documentos de referência no diretório References/:

  • 01-cordis-core.md — Framework Cordis (contexto, fiber, efeitos)
  • 02-schemastery.md — Validação de schema de configuração
  • 03-tools.md — Sistema de ferramentas detalhado ((defineTool), estratégias)
  • 04-capability-layering.md (na verdade 08-capability-layering.md) — Padrão de design de três papéis
  • 05-llm-adapter.md — Tutorial de adaptador LLM
  • 06-session-events.md — Eventos de sessão e fluxo de turnos
  • 07-debugging.md — Depuração e registro
  • 10-spatiotemporal.md — Modelo teórico (efeitos espaciais/temporais)
  • 11-cookbook.md — Padrões comuns e receitas

Copyright (c) 2025 DeepSeek Harness Plugin Dev Skill. Licenciado sob MIT.

Habilidades Relacionadas