hobots
v2.0.1
Published
SDK JavaScript da Hobots — telemetria (erros, transactions e heartbeats) e execução remota de tarefas sob demanda (tasks) para RPAs e integrações. Um único import e um único init().
Readme
hobots
SDK JavaScript/TypeScript da Hobots para instrumentar agentes (RPAs) e integrações.
- Telemetria — o que o programa emite sozinho: erros com stack trace (módulo Issues do app), performance (transactions/etapas) e heartbeats de uptime (módulo Agentes).
- Tasks — execução sob demanda: o agente faz polling no app da Hobots, executa handlers registrados e reporta status e logs (módulo Solicitações).
Um único import e um único init() cobrem as duas capabilities. Cliente HTTP puro, sem dependências de runtime. Requer Node 18+ (usa o fetch global); a telemetria também roda no browser para captura de erros.
Instalação
npm install hobots
# ou: pnpm add hobots · yarn add hobotsimport * as Hobots from 'hobots'; // um import só — telemetria e tasksTambém funciona em CommonJS: const Hobots = require('hobots').
Um SDK, duas capabilities, um init()
O SDK expõe duas capabilities autenticadas pelo mesmo Client Secret do agente, configuradas em um único init():
| Capability | Como habilita | Para quê | Ciclo de vida |
| --- | --- | --- | --- |
| Telemetria | sempre ativa no init() | O que o programa emite sozinho: erros, transactions/etapas, heartbeat. | Timers unref'd — não segura o processo. Chame close() no fim. |
| Tasks | tasks: true (ou opções) no init() | Execução remota sob demanda via polling. | start() segura o processo vivo até stop()/close(). |
Você usa uma, outra ou as duas:
- Só saber se o robô quebrou, quanto demora e se está vivo? →
init({ clientSecret, heartbeat: true }). - Disparar o robô sob demanda a partir de um formulário no app? →
init({ clientSecret, tasks: true })+register()+start(). - Ambos? Passe
heartbeatetasksno mesmoinit()— oinstanceIdé a identidade única, e os dois canais convergem em um agente no painel. Comtasks: true, o heartbeat é opcional: o próprio poll de tasks já marca a presença do agente.
Hobots.init({
clientSecret: 'hb_<64 hex>',
instanceId: 'vm-cliente-01',
heartbeat: true, // telemetria: "estou vivo" a cada 30s
tasks: { pollIntervalMs: 5_000 }, // execução sob demanda
});
Hobots.register('processar-nfe', async (params, ctx) => {
ctx.log.info('começando…');
ctx.log.info('12 notas processadas'); // o retorno do handler é ignorado — reporte por log
});
Hobots.start(); // inicia o polling de tasks (segura o processo vivo)O Client Secret
Cada agente tem um único Client Secret — a mesma credencial autentica a telemetria e as tasks:
hb_<64 hex>A credencial é secreta e viaja sempre no header Authorization: Bearer — nunca na URL. Copie o Client Secret no app, em Configurações → Agentes — os snippets de integração já vêm prontos. O SDK fala com a API oficial https://api2.hobots.app automaticamente; em desenvolvimento, aponte para outro host com a env HOBOTS_API_URL (ex.: HOBOTS_API_URL=http://localhost:3001).
Telemetria
Todas as funções abaixo são no-op seguras se Hobots.init() não foi chamado (com aviso no console).
init(options)
Inicialize uma vez, o mais cedo possível no processo. Cria o cliente, instala handlers globais de erro (a menos que captureUnhandled: false) e inicia o heartbeat se configurado.
Hobots.init({
clientSecret: 'hb_<64 hex>',
environment: 'production',
release: '[email protected]',
instanceId: 'vm-cliente-01',
tags: { squad: 'automacoes' },
heartbeat: true, // "estou vivo" a cada 30s
});| Opção | Tipo | Default | Descrição |
| --- | --- | --- | --- |
| clientSecret | string | obrigatório | o Client Secret do agente (hb_<64 hex>), copiado em Configurações → Agentes |
| environment | string | 'production' | ambiente lógico |
| release | string | — | versão do programa, ex. [email protected] |
| instanceId | string | obrigatório | identidade única da instância — heartbeat e poll de tasks convergem nela |
| tags | Record<string,string> | — | tags aplicadas a todos os eventos |
| sampleRate | number | 1 | amostragem de erros (0..1) |
| tracesSampleRate | number | 1 | amostragem de transactions (0..1) |
| maxBreadcrumbs | number | 100 | tamanho do buffer de breadcrumbs |
| captureUnhandled | boolean | true | handlers globais de erro (uncaughtException/unhandledRejection) |
| heartbeat | boolean \| HeartbeatOptions | false | heartbeat periódico (true usa defaults) |
| tasks | boolean \| TasksOptions | false | habilita a execução sob demanda (true usa defaults) — veja Tasks |
| beforeSend | (event) => event \| null | — | edita/descarta evento (retorne null para descartar) |
| debug | boolean | false | loga atividade do SDK |
| flushIntervalMs | number | 2000 | intervalo do flush automático do transport |
| maxBatchSize | number | 10 | itens por envelope antes do flush imediato |
Captura de erros e mensagens
try {
await processarNota(nota);
} catch (error) {
Hobots.captureException(error, {
type: 'error',
tags: { cliente: 'acme' },
fingerprint: ['sefaz-timeout'], // agrupa manualmente; sem isso o servidor agrupa pelo stack
});
throw error;
}
Hobots.captureMessage('Lote processado com sucesso', 'success');Ambas retornam o event_id (ou undefined se não inicializado). O hint aceita type, tags, extra e fingerprint. Erros não tratados são capturados e flushados automaticamente antes do processo sair — desligue com captureUnhandled: false. Os eventos aparecem agrupados em issues no módulo Issues do app.
EventType = 'fatal' | 'error' | 'warning' | 'success'. Não há 'info'/'debug': narração não abre issue — para isso existem o log da execução e as breadcrumbs. Use 'success' para reportar itens concluídos com êxito — contam como eventos, mas não viram issue nem disparam alertas.
Breadcrumbs
Trilha do que aconteceu antes do erro; enviada junto do próximo evento (buffer limitado por maxBreadcrumbs, mais antigos descartados).
Hobots.addBreadcrumb({ category: 'nfe', message: 'Baixou XML da nota 123', level: 'info' });A lista não é limpa ao enviar um evento e é global. Conceito: Breadcrumb.
Escopo: tags, usuário, contexto, extras
Dados anexados a todos os eventos seguintes:
Hobots.setTag('regiao', 'nordeste');
Hobots.setTags({ squad: 'automacoes', cliente: 'acme' });
Hobots.setUser({ id: 'op-42', email: '[email protected]' });
Hobots.setContext('vm', { cpu: 4, ram: '8GB' });
Hobots.setExtra('ultimoLote', 128);Para um escopo isolado (sem vazar para o global), use withScope:
Hobots.withScope((scope) => {
scope.setTag('lote', '128').setFingerprint(['reprocessamento']);
Hobots.captureMessage('Reprocessando lote 128');
});Performance: transactions e etapas
const tx = Hobots.startTransaction('processar-lote', 'rpa.job');
const step = tx.startChild('http.client', 'GET /api/notas');
await api.buscarNotas();
step.setData('notas', 42).finish();
const filho = step.startChild('db.query', 'INSERT INTO notas'); // etapas aninham
await gravar();
filho.finish();
tx.setStatus('ok'); // ou 'error'
tx.finish(); // só aqui a transaction é enviada (respeitando tracesSampleRate)startChild(op?, description?)existe emTransactioneStep(aninhamento arbitrário).- Métodos encadeáveis:
setStatus,setData(step),setTag/setName(transaction). finish()na transaction fecha as etapas abertas e envia o payload; nada é enviado se ela não foi amostrada. A amostragem é decidida nostartTransaction().
Dentro de um handler de task, startTransaction() retorna a transaction da própria run (ctx.tx) em vez de criar uma segunda — cada execução tem exatamente uma transaction. Conceitos: Transaction · Step.
Heartbeat (uptime)
Bate "estou vivo" periodicamente; o servidor acusa o robô offline quando os batimentos param.
Hobots.init({
clientSecret,
heartbeat: { intervalMs: 30_000, name: 'Robô NFe — VM 01' },
});heartbeat: true usa os defaults. A identidade é o instanceId do init(); os campos são intervalMs, name (default: o instanceId) e metadata — valores em Tempos e intervalos. O primeiro batimento sai na hora em que o init() roda, não depois do primeiro intervalo.
O heartbeat é essencial para agentes só de telemetria; com tasks: true ele é opcional, porque o poll de tasks já marca a presença do agente no painel. Conceito: Heartbeat.
Flush e close
await Hobots.flush(2000); // envia o buffer, mantém o SDK vivo
await Hobots.close(2000); // flush final + para o heartbeat + fecha o transportChame close antes do processo sair. Os timers da telemetria são unref'd — ela não segura o processo vivo sozinha.
Tasks (execução sob demanda)
Capability de execução remota sob demanda, habilitada com tasks: true (ou opções) no init(). O agente faz polling no app, executa os handlers registrados quando aparece trabalho e reporta status e logs incrementais — cada execução aparece no módulo Solicitações do app. Mesmo Client Secret, mesmo debug e o instanceId como identidade — nenhum handler global extra é instalado.
Ciclo de vida
Hobots.init({
clientSecret: 'hb_<64 hex>',
instanceId: 'vm-cliente-01',
tasks: true, // ou { pollIntervalMs, concurrency, ... }
});
Hobots.register('processar-nfe', async (params, ctx) => {
ctx.log.info(`processando lote ${params.lote}…`);
ctx.throwIfCanceled(); // cancelamento cooperativo
ctx.log.info('12 notas processadas');
});
Hobots.start(); // começa o polling e MANTÉM O PROCESSO VIVO
process.on('SIGINT', () => {
void Hobots.close(15_000).then(() => process.exit(0));
});init({ tasks })— cria o agente. Chamarinit()de novo para o agente anterior e o substitui (o registry de handlers é preservado).register(task, handler)—taské otask_slugconfigurado no formulário do app (Configurações → Formulários). Registre antes destart(). Funciona até antes doinit(); com as tasks desabilitadas, avisa no console.start()— começa o polling. Lança se oinit()não configuroutasksou se nenhuma task foi registrada.stop(timeoutMs?)— para só o polling de tasks: encerra o polling e aguarda as runs ativas (default 30s); a telemetria continua. Retornafalsese estourou o timeout.close(timeoutMs?)— encerra tudo: agente (aguardando runs em voo) → heartbeat → flush final do transport.
| Opção (TasksOptions) | Tipo | Default | Descrição |
| --- | --- | --- | --- |
| pollIntervalMs | number | 5000 | intervalo entre polls sem trabalho (mínimo 1000) |
| concurrency | number | 1 | runs simultâneas (clamp 1..5) |
| taskTimeoutMs | number | — | timeout client-side por run (não mata o handler; servidor é o backstop) |
| logFlushIntervalMs | number | 2000 | intervalo do envio incremental de logs |
| cancelCheckIntervalMs | number | 5000 | heartbeat de cancelamento por run (clamp 1000..15000) — também é o sinal de vida da run |
| installExitHandlers | boolean | true | handlers de SIGINT/SIGTERM/uncaughtException que reportam as runs ativas como AgentTerminated antes do processo morrer; nunca interferem quando o programa tem handler próprio |
O Client Secret, o debug e a identidade do agente (instanceId) vêm do init() — o heartbeat e o poll de tasks convergem em um agente no painel automaticamente.
O handler e o RunContext
type TaskHandler<P = Record<string, unknown>> =
(params: P, ctx: RunContext) => void | Promise<void>;
interface RunContext {
runId: string;
task: string;
log: RunLogger; // ctx.log.debug/info/warn/error/success(message)
items: RunItems; // ctx.items.succeeded/failed/occurrence — a entrega do bot
tx: Transaction; // transaction desta run — correlacionada e finalizada pelo SDK
signal: AbortSignal;
throwIfCanceled(): void; // lança RunCanceled se o signal já disparou
sleep(ms: number): Promise<void>; // espera cancelável — rejeita com RunCanceled no abort
}params— os dados do formulário configurado no app (JSON validado). Tipável:Hobots.register<{ lote: number }>(...).ctx.log.<level>(message)— níveisdebug,info,warn,erroresuccess; logs enviados incrementalmente ao app (PATCHes com throttle), visíveis na solicitação quase em tempo real; também impressos no terminal local, coloridos por nível.ctx.items— registra os itens que a execução processou/gerou (a entrega do bot: notas fiscais baixadas, boletos emitidos…), cada um com statussucceeded,failed(falha técnica) ouoccurrence(ocorrência de negócio, ex.: "nota nº 10 não encontrada") e umpayloadde atributos livres. Aparecem na aba itens da solicitação, com totalizadores; as colunas exibidas são configuradas por formulário no app. Guia: Itens de execução.ctx.tx— a transaction de performance desta run, já correlacionada e finalizada pelo SDK. Usectx.tx.startChild(op, description)para medir as etapas — elas viram o waterfall da execução no app.startTransaction()chamado dentro do handler retorna essa mesma transaction. Conceito: ctx.tx.ctx.signal—AbortSignalque dispara quando a run é cancelada no app, em timeout ou no shutdown.ctx.throwIfCanceled()— o ponto de parada de uma linha; lançaRunCanceledse osignaljá disparou.ctx.sleep(ms)— espera que respeita osignal(rejeita comRunCanceledem vez de dormir até o fim).- Retorno — ignorado: o SDK não captura nem envia o valor retornado pelo handler (resultados podem conter dados sensíveis e não são coletados).
- Erro lançado — a run é reportada como
failed(mensagem cap 10k, stack 20k).
Dados sensíveis:
ctx.loge mensagens/stack de erros são enviados ao servidor e ficam visíveis no app. Não inclua senhas, tokens, dados pessoais ou qualquer informação sensível em logs e mensagens de erro.
Itens de execução (ctx.items)
Hobots.register('baixar-notas', async (params, ctx) => {
for (const nota of await listarNotas(params)) {
try {
await baixar(nota);
ctx.items.succeeded({ numero_nota: nota.numero, cnpj_prestador: nota.cnpj }, { id: nota.numero });
} catch (erro) {
ctx.items.failed(String(erro), { numero_nota: nota.numero }, { id: nota.numero });
}
}
// ocorrência de negócio (não é falha do bot):
// ctx.items.occurrence('nota nº 10 não encontrada', { numero_nota: '10' }, { id: '10' })
});Chamadas síncronas e bufferizadas (lotes de até 500; dreno antes do reporte final). O id opcional é a identidade de negócio do item na run — repetido, é ignorado pelo servidor (registro único, retry idempotente). Limites: 10.000 itens/run, payload 16 KB, mensagem 2.000 caracteres. Detalhes em Itens de execução.
Anexos (screenshots)
Texto de log conta o que o robô fez; um print conta como estava a tela. Os métodos de captura (Hobots.captureException e Hobots.captureMessage) aceitam um attachment no hint: dentro de uma execução de task, a imagem vira anexo da linha de log da run que espelha a captura — no app ela aparece como miniatura logo abaixo dessa linha, clicável para ampliar.
Hobots.register('processar-nfe', async (params, ctx) => {
await entrar();
Hobots.captureMessage('portal após o login', 'info', {
attachment: await page.screenshot(),
});
try {
await emitir(params);
} catch (erro) {
Hobots.captureException(erro, {
attachment: { image: await page.screenshot(), caption: 'tela no momento do erro' },
});
throw erro;
}
});O SDK não tira o print — ele recebe a imagem pronta, de onde você quiser (Playwright, Puppeteer, uma lib de captura de desktop, um arquivo em disco). O attachment aceita bytes (Uint8Array/ArrayBuffer/Buffer), um caminho de arquivo (só em Node) ou um objeto { image, caption?, contentType? }.
| Limite | Valor | | --- | --- | | formatos | PNG, JPEG, WEBP | | tamanho por imagem | 5 MB | | anexos por execução | 20 | | anexos por linha de log | 4 |
A linha de log é registrada na hora; o upload roda em segundo plano e vai direto para o S3 por URL presignada, sem passar pela API — a captura nunca bloqueia nem lança por causa do anexo (falhas viram um aviso no log da run). Fora de uma execução de task não há onde pendurar a imagem: o attachment é descartado com um aviso. Detalhes em Anexos.
Dados sensíveis: o print é enviado exatamente como está — o SDK não borra nem recorta nada, e telas de portal costumam conter CPF, nome e valores. Capture só o necessário. Solicitantes só veem os anexos quando o projeto está com "mostrar logs para solicitantes" ligado, o mesmo gate dos logs.
Cancelamento cooperativo
Uma run cancelada no app não mata o handler — o SDK apenas dispara ctx.signal. Chame ctx.throwIfCanceled() em loops longos e pontos de parada (e troque esperas longas por ctx.sleep()):
Hobots.register('processar-nfe', async (params, ctx) => {
for (const nota of params.notas) {
ctx.throwIfCanceled(); // lança RunCanceled se cancelada — o handler para aqui
await processar(nota);
}
});Quando o cancelamento veio do app, o RunCanceled lançado é suprimido — a run já está cancelada no servidor e nada é reportado por cima. Para limpeza antes de parar (fechar navegador, desfazer estado), use try/finally.
Atenção: não use
if (ctx.signal.aborted) return;. Retornar normalmente faz o SDK reportar a run comosucceeded— uma run cancelada apareceria como sucesso no app. É preciso lançar, e é o que othrowIfCanceled()faz.
Concorrência
concurrency (default 1, máx 5) controla quantas runs o agente executa em paralelo. Com 1, as runs são sequenciais. O agente só reivindica o que consegue rodar (max = concurrency - runs ativas) e entra em drain mode: re-pesquisa imediatamente enquanto vem trabalho; quando não vem, espera pollIntervalMs.
Timeout por run
taskTimeoutMs é client-side. No estouro o SDK não mata o handler — dispara ctx.signal, libera o slot e reporta a run como falha (TaskTimeout). Respeite o signal para parar de verdade. Sem ele, o backstop é o servidor.
Idempotência (at-least-once até running)
Se o agente morrer logo após capturar uma run (antes de reportar running), ela é re-entregue quando um agente voltar (até 3 tentativas; depois AgentLost). A partir do momento em que a run está running, a morte do agente não re-entrega — a run vira falha AgentTerminated, porque o trabalho pode ter efeitos colaterais parciais e re-executar automaticamente não é seguro (reexecute manualmente pelo app).
Ainda assim, escreva handlers idempotentes: a janela de re-entrega pós-claim existe, e processar a mesma run duas vezes não pode causar efeito duplicado (use uma chave de deduplicação sua, como o número da nota).
Encerramento gracioso
close(timeoutMs?) encerra tudo na ordem certa: para o polling de tasks (aguardando a run atual), para o heartbeat e faz o flush final do transport.
// só telemetria: não segura o processo, mas faça o flush final para não perder eventos
await Hobots.close(2_000);
// com tasks: segura o processo por design — aguarde a run atual
process.on('SIGINT', () => {
void Hobots.close(15_000).then(() => process.exit(0));
});Para parar de aceitar execuções sem derrubar a telemetria, use stop(timeoutMs?).
Entry points / exports
| Import | Conteúdo |
| --- | --- |
| hobots | Tudo: init, telemetria (captura, transactions, heartbeat), tasks (register/start/stop), close, classes e tipos. |
| hobots/protocol | Tipos do wire format (telemetria e tasks). |
Empacotado em ESM e CommonJS com definições de tipos (.d.ts) para todas as entradas.
Documentação completa
Guias detalhados acompanham o pacote em docs/.
Novo no vocabulário de observabilidade (transaction, step, breadcrumb, tag, context, extra, flush, close)? Comece por Conceitos — as outras páginas assumem esses termos.
- Conceitos — o que cada coisa é, por que existe e quando escolher uma em vez da outra.
- Getting started — instalação, Client Secret, primeiro
init, telemetria e tasks. - Telemetria — erros, breadcrumbs, escopo, transactions/etapas, heartbeat.
- Tasks — handlers,
RunContext, cancelamento, concorrência, idempotência. - Referência de API — todas as funções e tabelas de opções com defaults.
- Protocolo & transporte — o Client Secret, endpoints, envelope, retries.
Licença
MIT © Hobots
