DeepSeek Harness: Ciclo de Vida do Plugin
Última atualização: 2026-08-31
Cada plugin no Cordis não é um bloco de código estático, mas uma entidade viva com um ciclo de vida — um Fiber. Entender a Fiber state machine significa entender a jornada completa de "aguardando para carregar" a "executando" a "saída graciosa," que é a chave para escrever plugins robustos.
📋 Pré-requisitos: Ter completado 13-effect.md, entender auto-limpeza e ctx.effect()
1. O Que Você Vai Aprender
- Conceito Fiber: contêiner de estado de um plugin
- Ciclo de vida: pending → active → disposing → disposed
- Criação e destruição de Fiber
- Relações Fiber pai-filho
- Tratamento de erros e rollback de estado
- Isolamento de contexto Fiber
2. Conceito Fiber
(1) O Que É Fiber
▶ Exemplo 1: Interface Fiber
Fiber é a abstração do Cordis sobre o estado de runtime de um plugin — cada instância de plugin corresponde a um objeto Fiber que registra em qual estágio do ciclo de vida o plugin está atualmente.
interface Fiber {
id: string
name: string
state: FiberState
context: Context
parent: Fiber | null
children: Fiber[]
}
(2) Por Que Fiber É Necessário
Sem Fiber, plugins têm apenas dois estados: "carregado" e "descarregado." Mas na prática:
- Quando dependências não estão prontas, um plugin deve "aguardar" em vez de "falhar"
- Durante descarregamento, um plugin precisa estar "limpando" em vez de apenas desaparecer
- Em erro, um plugin pode precisar "tentar novamente" em vez de "morrer"
Fiber fornece um modelo claro de state machine para esses estados intermediários.
(3) Fiber e Context
Fiber = "alma" do plugin (informação de estado)
Context = "corpo" do plugin (recursos e ambiente)
Cada Fiber possui um Context; o ciclo de vida do Context está vinculado ao Fiber.
3. Estados do Ciclo de Vida
(1) Diagrama de Estados
stateDiagram-v2
[*] --> pending: Plugin registrado
pending --> active: Dependências prontas + apply bem-sucedido
pending --> errored: apply falha
active --> disposing: Descarregamento solicitado
errored --> active: Tentativa bem-sucedida
errored --> disposing: Tentativa abandonada
disposing --> disposed: Limpeza concluída
(2) Significados dos Estados
| Estado | Significado | ctx Disponível | Reversível |
|---|---|---|---|
| pending | Aguardando dependências | Limitado | ✅ |
| active | Executando normalmente | Completo | ✅ |
| disposing | Limpando recursos | Somente leitura | ❌ |
| disposed | Destruído | Indisponível | ❌ |
| errored | Falha na inicialização | Indisponível | ✅ |
(3) Estado pending
Quando um plugin declara inject mas as dependências ainda não foram registradas, Fiber entra em pending:
export const inject = ['tools']
// Serviço tools ainda não registrado → Fiber está pending
// tools registrado → Fiber transiciona para active, chama apply
Durante pending:
- O apply do plugin ainda não foi chamado
- Serviços de dependência em ctx estão indisponíveis
- Transiciona automaticamente para active quando dependências ficam prontas
(4) Estado active
Após apply executar com sucesso, Fiber entra em active:
export function apply(ctx: Context) {
// Fiber agora está active
ctx.logger.info('Estou vivo!')
ctx.on('session/created', (s) => {
// Pode usar todos os serviços normalmente
})
}
Durante active:
- Todos os serviços declarados estão disponíveis
- Pode registrar novos recursos e listeners
- Pode responder a eventos e comandos
(5) Estado disposing
Após receber uma solicitação de descarregamento, Fiber entra em disposing e começa a limpar recursos:
Processo disposing:
1. Parar de aceitar novas requisições
2. Executar funções de limpeza ctx.effect() em ordem inversa
3. Auto-remover listeners de eventos
4. Limpar timers
5. Desregistrar comandos e serviços
(6) Estado disposed
Após todos os recursos serem limpos, Fiber entra em disposed:
- ctx não está mais disponível
- Qualquer chamada a ctx lançará erro
- Objeto Fiber é retido para auditoria e logging
4. Criação e Destruição de Fiber
(1) Momento de Criação
Fiber é automaticamente criado quando um plugin é registrado:
// Lógica interna do framework (pseudocódigo)
function registerPlugin(plugin: PluginDefinition) {
const fiber = new Fiber({
id: generateId(),
name: plugin.name,
state: hasDeps(plugin) ? 'pending' : 'active'
})
if (fiber.state === 'active') {
fiber.context = createContext(fiber)
plugin.apply(fiber.context)
}
}
(2) Gatilhos de Destruição
Destruição de Fiber é acionada por:
| Gatilho | Descrição |
|---|---|
ctx.dispose() |
Descarregamento ativo |
| Fiber pai destruído | Descarregamento em cascata |
| Recarregamento por mudança de config | Fiber antigo destruído, novo Fiber criado |
(3) Ordem de Destruição
graph TD
TRIGGER[Gatilho de descarregamento] --> STOP[Parar novas requisições]
STOP --> CHILD[Destruir Fibers filhos]
CHILD --> CLEAN[Executar funções de limpeza]
CLEAN --> DISPOSED[Marcar disposed]
5. Relações Fiber Pai-Filho
(1) Estrutura Hierárquica
Fibers suportam relações pai-filho, formando estruturas em árvore:
graph TD
ROOT[Root Fiber<br/>dsh-core] --> A[Plugin A Fiber]
ROOT --> B[Plugin B Fiber]
A --> A1[Sub-plugin A1]
A --> A2[Sub-plugin A2]
(2) Estabelecendo Relações Pai-Filho
▶ Exemplo 2: Criando Sub-Plugins via ctx.plugin()
Crie Fibers filhos via ctx.plugin():
export function apply(ctx: Context) {
ctx.plugin({
name: 'sub-plugin',
apply(subCtx: Context) {
subCtx.logger.info('Eu sou um fiber filho')
}
})
}
(3) Destruição em Cascata
Quando um Fiber pai é destruído, todos os Fibers filhos são automaticamente destruídos:
Descarregar Plugin A:
→ Primeiro destruir Sub-plugin A1
→ Depois destruir Sub-plugin A2
→ Finalmente destruir Plugin A
Esta ordem de destruição "filhos antes do pai" garante que dependências não sejam quebradas.
(4) Herança de Escopo
Fibers filhos herdam o ctx do Fiber pai:
// Serviços registrados pelo plugin pai são acessíveis aos plugins filhos
export function apply(ctx: Context) {
ctx.provide('parent-service', { ... })
ctx.plugin({
name: 'child',
inject: ['parent-service'],
apply(childCtx) {
childCtx['parent-service'] // ✅ Pode acessar serviço do pai
}
})
}
6. Tratamento de Erros e Rollback de Estado
(1) Falha no apply
Quando apply lança uma exceção, Fiber entra no estado errored:
export function apply(ctx: Context) {
throw new Error('inicialização falhou')
// Fiber → errored
}
(2) Tentativa Automática
Cordis automaticamente tenta novamente Fibers com erro:
1ª tentativa: apply() → throw Error → errored
2ª tentativa: (aguardar 1s) apply() → throw Error → errored
3ª tentativa: (aguardar 2s) apply() → throw Error → errored
4ª tentativa: (aguardar 4s) apply() → sucesso → active
O intervalo de tentativa usa backoff exponencial: 1s → 2s → 4s → 8s → ... → máx 60s.
(3) Configuração da Estratégia de Tentativa
export const Config = Schema.object({
maxRetries: Schema.number().default(5).description('Máximo de tentativas'),
retryInterval: Schema.number().default(1000).description('Intervalo inicial de tentativa (ms)')
})
(4) Tentativa Manual
ctx.on('fiber/errored', (fiber) => {
ctx.logger.warn(`plugin ${fiber.name} com erro, tentando novamente...`)
fiber.retry()
})
(5) Erros Não Retentáveis
Alguns erros não devem ser tentados novamente:
export function apply(ctx: Context) {
if (!process.env.REQUIRED_VAR) {
// Erro de configuração, tentar novamente não ajuda
throw new NonRetryableError('REQUIRED_VAR não está definida')
}
}
(6) Erros na Fase de Limpeza
Erros durante a fase disposing não impedem Fiber de transicionar para disposed, mas são registrados:
[warn] erro de limpeza no plugin my-plugin: Conexão já fechada
[info] plugin my-plugin disposed (com 1 avisos de limpeza)
7. Isolamento de Contexto Fiber
(1) Cada Fiber Tem um ctx Independente
const fiberA = new Fiber({ name: 'plugin-a' })
const fiberB = new Fiber({ name: 'plugin-b' })
fiberA.context !== fiberB.context // true
(2) Limites de Isolamento
| Recurso | Isolado | Descrição |
|---|---|---|
| Listeners de evento | ✅ | Cada Fiber registra independentemente |
| Timers | ✅ | Cada Fiber limpa independentemente |
| Comandos | ⚠️ | Compartilhados globalmente, mas com escopos |
| Serviços | ❌ | Compartilhados globalmente |
| Configuração | ✅ | Cada plugin é independente |
(3) Equilibrando Compartilhamento e Isolamento de Serviços
Serviços são compartilhados globalmente — esta é uma decisão de design do Cordis. O serviço A registrado pelo plugin A pode ser usado pelo plugin B via inject. Se isolamento é necessário, use o mecanismo de escopo (veja 20-scope.md).
(4) Consultas de Estado
▶ Exemplo 3: Consultando Estado do Fiber
// Consultar estado do Fiber
ctx.fiber.state // 'active'
ctx.fiber.id // 'fiber-abc-123'
ctx.fiber.parent // Fiber pai ou null
ctx.fiber.children // Fiber[]
❓ Perguntas Frequentes
ctx.plugin(), o framework automaticamente cria Fibers.typescript ctx.on('fiber/created', (fiber) => { ... }) ctx.on('fiber/active', (fiber) => { ... }) ctx.on('fiber/disposing', (fiber) => { ... }) ctx.on('fiber/disposed', (fiber) => { ... }) ctx.on('fiber/errored', (fiber) => { ... }) 📖 Resumo
- Fiber é a abstração do Cordis sobre o estado de runtime do plugin; cada instância de plugin corresponde a um Fiber
- Ciclo de vida: pending (aguardando deps) → active (executando) → disposing (limpando) → disposed (destruído)
- Fibers pai-filho suportam destruição em cascata na ordem "filhos antes do pai"
- Em falha do apply, Fiber entra em errored com tentativa automática de backoff exponencial
- Cada Fiber tem seu próprio Context independente; listeners de evento e timers são isolados, serviços são compartilhados globalmente
- Monitore estado de plugins através de eventos de ciclo de vida do Fiber
📝 Exercícios
1. ⭐ Básico: Escreva um plugin que exiba o estado e id do Fiber atual em apply. Inicie e verifique os logs para confirmar que o Fiber está no estado active.
2. ⭐⭐ Intermediário: Escreva um plugin que deliberadamente lança um erro em apply (simulando falha de inicialização). Observe o estado errored do Fiber e o comportamento de tentativa. Depois corrija o erro e verifique que o Fiber recupera para active.
3. ⭐⭐⭐ Desafio: Crie uma estrutura Fiber pai-filho: o plugin pai registra um serviço, o plugin filho o usa via inject. Descarregue o plugin pai e verifique que o filho também é destruído em cascata. Exiba a ordem de destruição nas funções de limpeza para confirmar "filhos antes do pai."