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.sessionsou eventos comotools/*,agent/*,session/eventno 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):
| Conceito | Significado | Manifestaçã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 é descarregado | ctx.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 automaticamente | export 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 vida | PENDING → LOADING → ACTIVE → UNLOADING → DISPOSED (se apply lançar erro, fica FAILED) |
Regras de Ferro resultantes:
- Todo registro deve ser feito através de
ctx(ctx.on/ctx.tools.register/ctx.effect/ctx.plugin/ctx.provide). Tudo que é registrado viactxé limpo automaticamente no descarregamento. Nunca useremoveListener/clearIntervalmanualmente. - Qualquer recurso não gerenciado por
ctx(conexões de rede, timers próprios, file handles, watchers) deve ser envolvido emctx.effect(() => { ...; return () => cleanup }). - Dependências devem ser declaradas (com
inject). Não usectx.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 emcordis.yml. - 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 nocordis.ymlsem modificar o código?). - 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.
- 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—defineToole 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-credentialsetc. (lista completa emReferences/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 compnpm dsh web --patch <seu-cordis.yml>, execute exemplos de tutorial Cordis puro comnode --import tsx ../../vendor/cordis/bin.js. - CLI instalado:
dsh --profile <nome> ...para iniciar um profile,dsh plugin --profile <nome> <args pnpm>para gerenciar plugins.
- Checkout do código-fonte (recomendado para tutoriais): clone
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
- Forma de Função (recomendada por padrão):
export const name = 'greet-tool' export const inject = ['tools'] export function apply(ctx: Context) { /* ... */ } - Forma de Objeto:
export default { name, inject, apply(ctx) {} } - 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 terrequired: true. Suportastring/number/integer/boolean/null/array/object/oneOf; nós de objeto explícito devem declararadditionalProperties: true | false.output.schema: Declara o valor JSON canônico retornado porexecute(objeto/array/escalar/null são aceitos). Esta é a API para chamadores programáticos (oawait tools.xxx()do Code Mode), projetada para retornar diretamente handles e campos; a explicação para humanos vai emoutput.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.
- 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
output.render(args, value): Função pura que converte o valor canônico emContentBlock[](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,finalizeContentno schema visível ao modelo – eles não vazam para o modelo. - Parâmetros são imutáveis: trate
argscomo 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ão | Modo | Uso |
|---|---|---|
tools/pre-execute | waterfall | Estratégias de permitir/negar/perguntar (controle de acesso); retorna {kind:'allow'} / {kind:'deny',reason} / {kind:'ask'} |
ctx.tools.guard() | Guarda monotônica | Negação final; listeners subsequentes não podem desfazer (retorna reason = negação) |
tools/execute | waterfall | Encapsular distribuição: timeout/retry/métricas; pode substituir exec.signal mas não pode remover |
tools/post-execute | waterfall | Substituir conteúdo/valor, impedir resultado, anexar contexto adicional ao modelo |
tools/result | emit | Observaçã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:
Configdeve 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
!!jspara avaliação no carregamento (ex.:greeting: !!js process.env.GREETING ?? 'Hello');!!jsé válido apenas nos camposconfigedisabled. - 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)
injectnã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,llmetc. 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 +isolatepara que diferentes grupos de plugins vejam instâncias diferentes do mesmo nome de serviço (ex.: dois grupos cada um com seu próprio provedorshellcom timeout diferente).
7. Sistema de Eventos (Comunicação Entre Plugins)
Modos de Distribuição (Parte do Nome do Evento, Deve Ser Escolhido Corretamente)
| Modo | Chamada | Semântica |
|---|---|---|
emit | ctx.emit(name, ...args) | Broadcast síncrono, não espera, não coleta retornos |
parallel | await ctx.parallel(...) | Todos os listeners executam concorrentemente e aguarda |
serial | await ctx.serial(...) | Executa sequencialmente, o primeiro retorno não-null/false/undefined vence e para |
bail | ctx.bail(...) | Versão síncrona do serial |
waterfall | await 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-stoppingetc.) — ciclo de vida do agente e construção de requisiçõessession/event— fluxo de eventos de sessão persistido (turn/*,step/*,tool/call,tool/result,assistant/chunksão todosevent.typeaqui, não eventos Cordis de mesmo nome)approval/request— waterfall de aprovaçãosystem-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 umErrorcomum. - Provedor não suporta um campo de
GenerateOptions:throw new LlmError(..., 'UNSUPPORTED'), nunca ignore silenciosamente. - Cada requisição HTTP deve mesclar
attributionHeaders()e passaroptions.signal. - Opcional: sobrescrever
resolveModel()(retorna identidade do provedor/modelo + metadados opcionais de context/reasoning) elistModels()(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:
- 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> } - Provedor do Serviço (ex.:
dsh-bash-local): Implementa a interface.class MyCapLocal extends MyCapService { ... };apply(ctx){ ctx.plugin(MyCapLocal) } - 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 declaradsh.profile(contendo uma lista ordenada debundles). É 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 emdsh.profile.bundlesempilha 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.effectcom cleanup que restaura o estado anterior. - Hooks de ciclo de vida: além de
apply, o plugin pode exportardispose()(chamado quando instância antiga é descarregada antes da recarga). Raramente necessário se você usactx.effect. - Cuidado com timers:
setIntervalsemctx.effectsobrevive 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 porDSH_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.ymlcom apenas as dependências essenciais, iniciando compnpm dsh web --patch debug.yml. - Ferramenta de rastreamento de estado Cordis: veja
References/07-debugging.mdpara 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())? NenhumremoveListenermanual? - 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,presentResultsã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.schemadeclara 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ção03-tools.md— Sistema de ferramentas detalhado ((defineTool), estratégias)04-capability-layering.md(na verdade08-capability-layering.md) — Padrão de design de três papéis05-llm-adapter.md— Tutorial de adaptador LLM06-session-events.md— Eventos de sessão e fluxo de turnos07-debugging.md— Depuração e registro10-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.