Tutoriais · Plugins
Protocolo do sidecar
Como um sidecar conversa com o Jarvis ADE: variáveis de ambiente, mensagens de entrada e saída em JSON lines, ciclo de vida, restart e backoff.
Nesta página
Visão geral
Um sidecar é um processo filho que o Jarvis ADE inicia com o comando do manifesto, com a pasta instalada do plugin como cwd. A conversa é em JSON lines: um objeto JSON por linha, sem quebras de linha dentro dele.
- stdin (app → sidecar):
hello, eventos de missão e do quadro, eshutdown. - stdout (sidecar → app):
ready,log,card.note,card.link,resync,error. - stderr: cada linha vira um log de nível
warn.
O sidecar só é iniciado quando o plugin está ativado e todas as configurações com required: true têm valor. Uma falha do sidecar (linha ilegível, crash, timeout) é registrada no log da integração e nunca derruba o app.
Variáveis de ambiente
O processo herda o process.env do app, mais o env declarado na integração, mais as variáveis abaixo (as ADE_* vencem em caso de conflito):
| Variável | Valor |
|---|---|
| ADE_PLUGIN_ID | o id do plugin. |
| ADE_PLUGIN_INTEGRATION_ID | o id da integração. |
| ADE_PLUGIN_DATA_DIR | caminho absoluto userData/plugin-data/<plugin>/<integração>. O app cria a pasta antes de iniciar; é o lugar para estado, cache e temporários. |
| ADE_PLUGIN_SETTINGS | JSON plano de strings: os default do manifesto com os valores que o usuário preencheu por cima. Faça JSON.parse com try/catch. |
| ADE_PROTOCOL_VERSION | sempre "1" hoje. |
| ADE_LOCALE | idioma do app (ex.: pt-BR). |
Mensagens do app para o sidecar (stdin)
Toda linha tem o envelope { v: 1, id, at, type, ...payload }: v é a versão do protocolo, id um UUID por mensagem e at o Date.now() da emissão.
{"v":1,"id":"6f1c…","at":1789744976234,"type":"hello","adeVersion":"0.1.0","pluginId":"hello-ade","integrationId":"echo","settings":{"greeting":"hello"}}{
"v": 1,
"id": "0b8d…",
"at": 1789745131936,
"type": "board.card_moved",
"mission": { "id": "…", "name": "…", "workspaceId": "…", "workspaceName": "…", "status": "running", "createdAt": 1789744899624 },
"columns": [{ "id": "…", "title": "Backlog", "order": 0, "role": "backlog" }],
"card": { "id": "…", "title": "…", "columnId": "…", "assignee": null, "columnTitle": "Em desenvolvimento", "columnRole": "dev" },
"from": { "id": "…", "title": "Backlog", "order": 0, "role": "backlog" },
"to": { "id": "…", "title": "Em desenvolvimento", "order": 1, "role": "dev" }
}| type | Quando | Campos além do envelope |
|---|---|---|
| hello | Logo após cada início do processo | adeVersion, pluginId, integrationId, settings |
| board.snapshot | Depois do hello, um por missão com quadro ativo; e de novo por resync | mission, columns, cards |
| mission.created | Missão criada | mission |
| mission.renamed | Missão renomeada | mission |
| mission.finished | Missão encerrada | mission, status, summary (pode ser null) |
| board.columns_set | Colunas definidas/reordenadas | mission, columns |
| board.card_created | Card criado | mission, columns, card |
| board.card_updated | Card atualizado | mission, columns, card, changed (lista de title | description | docs | assignee | parent), note (nota de atividade dessa atualização, ou null) |
| board.card_moved | Card movido de coluna | mission, columns, card, from, to |
| board.card_deleted | Cards removidos | mission, cardIds |
| board.card_attached | Evidência anexada | mission, columns, card, attachment (path absoluto, legível na mesma máquina) |
| board.card_question_asked / _answered | Pergunta feita / respondida | mission, columns, card, question |
| shutdown | O app vai encerrar o sidecar | nenhum |
Formas que se repetem
mission:id,name,workspaceId,workspaceName,status(draft | planning | running | paused | merging | done | aborted | failed),createdAt(ms).- coluna:
id,title,order,role(backlog | dev | qa_wait | qa_test | bug | doneounull). card: o card completo (id,missionId,columnId,parentId,typestory | task | subtask,title,description,docs,activity,attachments,questions,order,createdAt,updatedAt…) maisassignee({ paneId, label, agentId }ounull),columnTitleecolumnRole.
Mensagens do sidecar para o app (stdout)
{"type":"ready","name":"meu-plugin"}
{"type":"log","level":"info","message":"sincronizado"}
{"type":"card.note","cardId":"<id do card>","message":"✅ espelhado no serviço"}
{"type":"card.link","cardId":"<id do card>","url":"https://exemplo.com/t/123","label":"Tarefa #123"}
{"type":"resync"}
{"type":"error","message":"falha ao autenticar"}| type | Campos | Efeito |
|---|---|---|
| ready | name? | Estado running. Envie assim que terminar de inicializar. |
| log | level (info | warn | error), message | Entra no painel de log da integração. Nível desconhecido vira info. |
| card.note | cardId, message | Nota na atividade do card, com autor plugin:<id do plugin>, sem checagem de atribuição. |
| card.link | cardId, url, label | Atalho para uma nota 🔗 <label>: <url>. |
| resync | missionId? | Reenvia board.snapshot da missão (ou de todas, sem missionId). |
| error | message | Entra no log como error; o processo continua rodando. |
- Uma linha do stdout que não é JSON (ou é JSON sem
typetextual, ou com umtypedesconhecido) vira um loginfocom o texto da linha — útil para depurar, mas prefira mensagenslogexplícitas. - Cada mensagem de log é cortada em 500 caracteres; o painel guarda as últimas 50 entradas.
- Um
card.notepara um card que não existe mais gera um aviso no log — não derruba nada.
Ciclo de vida, restart e backoff
| Estado | Significado |
|---|---|
| disabled | O plugin está desativado. |
| missing_settings | Falta valor em uma configuração required; o processo não é iniciado. |
| starting | Do início do processo até o ready — ou até 10 s. |
| running | Recebendo eventos. |
| stopped | Parado de propósito (restart, desativação, remoção, fim do app). |
| error | Desistiu: mais de 5 saídas inesperadas em 10 minutos, ou falha ao iniciar. |
| O quê | Valor |
|---|---|
| Espera pelo ready | 10 s. Sem ready, o estado vira running mesmo assim, com um aviso no log. |
| Encerramento gracioso | Linha shutdown → stdin fechado → 5 s de espera → SIGTERM. |
| Backoff após um crash | 1 s, 2 s, 4 s, 8 s, 16 s: o atraso depende de quantas saídas inesperadas houve nos últimos 10 minutos (1ª → 1 s … 5ª → 16 s). |
| Desistência | Mais de 5 saídas inesperadas em 10 minutos → error. O botão Reiniciar zera o contador. |
O que dispara um restart gracioso
- Salvar as configurações do plugin.
- Ativar o plugin, ou reimportá-lo.
- O botão Reiniciar na tela do plugin.
Em todo início do processo — restart gracioso, reinício depois de um crash com backoff, abertura do app — o sidecar recebe de novo o hello (com as configurações atuais) e os board.snapshot. Trate o hello como “começando do zero”, nunca como “o usuário acabou de fazer algo”. É exatamente por isso que ações disparadas por configuração precisam de uma trava anti-repetição.
Filtro de eventos
Com events no manifesto, só chegam os eventos de board.*/mission.* que combinam com um nome exato ou um prefixo algo.*. O filtro também vale para o board.snapshot. Sem filtro, ou com [], chega tudo.
Checklist de um sidecar correto
- Ler o stdin linha a linha (
node:readline) e fazerJSON.parsedentro de um try/catch. - Enviar
{ type: 'ready' }logo que estiver pronto. - Ao receber
shutdown(ou quando o stdin fechar), limpar e sair comprocess.exit(0)em menos de 5 s. - Só mensagens do protocolo no stdout — uma por linha. Tudo que for diagnóstico solto vai para o stderr ou para uma mensagem
log. - Nunca deixar uma exceção sem tratamento nem bloquear esperando I/O sem timeout.