hobots 1.3.5 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/manager.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Capability de **execução remota sob demanda**, habilitada com `manager: true` (ou opções) no `init()` único. O agente faz polling no app, executa os handlers registrados quando aparece trabalho e reporta status e logs incrementais. Usa o mesmo Client Secret, o mesmo `debug` e o `instanceId` como identidade — nenhum handler global extra é instalado.
4
4
 
5
+ O vocabulário desta página ([run](./concepts.md#run), [claim](./concepts.md#claim), [drain mode](./concepts.md#drain-mode), [ctx.tx](./concepts.md#transaction-de-run-ctxtx)) está explicado em [Conceitos](./concepts.md).
6
+
5
7
  ## Ciclo de vida
6
8
 
7
9
  ```js
@@ -26,10 +28,10 @@ process.on('SIGINT', () => {
26
28
  });
27
29
  ```
28
30
 
29
- - `init({ manager })` — cria o agente. Chamar `init()` de novo para o agente anterior e o substitui (os handlers registrados são preservados).
31
+ - `init({ manager })` — cria o agente. Chamar `init()` de novo para o agente anterior e o substitui (os handlers registrados são preservados). Se havia runs em execução, elas são interrompidas **sem espera** e reportadas como `AgentTerminated` na hora — `init()` não é um jeito gracioso de reconfigurar um agente ocupado; para isso use `stop()` antes.
30
32
  - `register(task, handler)` — `task` é o `task_slug` configurado no formulário do app. Registre **antes** de `start()`. Pode ser chamado antes do `init()`; se o `init()` não habilitou o Manager, o SDK avisa no console.
31
33
  - `start()` — começa o polling. Lança se o `init()` não configurou `manager` ou se nenhuma task foi registrada.
32
- - `stop(timeoutMs?)` — para **só o Manager**: encerra o polling e aguarda as runs ativas (default 30s); a telemetria continua. Retorna `false` se estourou o timeout.
34
+ - `stop(timeoutMs?)` — para **só o Manager**: encerra o polling e aguarda as runs ativas ([default](./api-reference.md#tempos-e-intervalos)); a telemetria continua. Retorna `false` se estourou o timeout.
33
35
  - `close(timeoutMs?)` — encerra **tudo**: agente (aguardando runs em voo) → heartbeat → flush final.
34
36
 
35
37
  O `instanceId` (obrigatório) é a identidade do agente no painel — o poll o envia como `agent.instance_id`, a **mesma identidade** do heartbeat do Monitor. Com o `init()` único, os dois canais convergem em **um agente** automaticamente.
@@ -46,6 +48,7 @@ interface RunContext {
46
48
  runId: string;
47
49
  task: string;
48
50
  log(level: 'debug' | 'info' | 'warning' | 'error', message: string): void;
51
+ tx: Transaction;
49
52
  signal: AbortSignal;
50
53
  throwIfCanceled(): void;
51
54
  sleep(ms: number): Promise<void>;
@@ -53,7 +56,8 @@ interface RunContext {
53
56
  ```
54
57
 
55
58
  - **`params`** — os dados do formulário configurado no app (o JSON validado).
56
- - **`ctx.log.<level>(message)`** — logs enviados **incrementalmente** ao app (PATCHes com throttle), visíveis na run em tempo quase real.
59
+ - **`ctx.log.<level>(message)`** — logs enviados **incrementalmente** ao app (PATCHes com throttle), visíveis na run em tempo quase real. Cada linha também é **impressa no terminal local** como `[hobots:<task>] INFO mensagem`, colorida por nível — útil para acompanhar o robô rodando na VM. A cor é desligada automaticamente fora de um TTY e quando a env `NO_COLOR` está definida; o eco local é independente do envio ao servidor.
60
+ - **`ctx.tx`** — a [transaction de performance](./concepts.md#transaction-de-run-ctxtx) desta run, já correlacionada e finalizada pelo SDK. Use `ctx.tx.startChild()` para medir as etapas (veja abaixo).
57
61
  - **`ctx.signal`** — `AbortSignal` que dispara quando a run é **cancelada no app**, em **timeout** ou no **shutdown**. Cancelamento é cooperativo (veja abaixo).
58
62
  - **`ctx.throwIfCanceled()`** — lança `RunCanceled` se o `signal` já disparou. O ponto de parada de 1 linha para chamar entre as etapas do trabalho.
59
63
  - **`ctx.sleep(ms)`** — sleep que respeita o `signal`: rejeita com `RunCanceled` no cancelamento em vez de esperar até o fim. Use no lugar do seu `sleep` em esperas longas.
@@ -64,9 +68,35 @@ Você pode tipar os params: `Hobots.register<{ lote: number }>(...)`.
64
68
 
65
69
  > **Dados sensíveis:** o que você escreve em `ctx.log()` e as mensagens/stack de erros lançados **são enviados ao servidor** e ficam visíveis na execução no app. Não inclua senhas, tokens, documentos, dados pessoais ou qualquer informação sensível em logs e mensagens de erro.
66
70
 
71
+ ## Performance da run (`ctx.tx`)
72
+
73
+ O SDK já abre uma [transaction](./concepts.md#transaction-de-run-ctxtx) para cada run e entrega em `ctx.tx`. Ela é correlacionada à execução, recebe o status conforme o desfecho (`ok`, `error` ou `cancelled`) e é **finalizada automaticamente** quando o handler termina. Você não cria e não finaliza — só marca as etapas:
74
+
75
+ ```js
76
+ Hobots.register('processar-nfe', async (params, ctx) => {
77
+ const login = ctx.tx.startChild('browser', 'login no portal');
78
+ await entrar();
79
+ login.finish();
80
+
81
+ for (const nota of params.notas) {
82
+ const span = ctx.tx.startChild('nfe', `nota ${nota.numero}`);
83
+ ctx.throwIfCanceled();
84
+ await processar(nota);
85
+ span.setData('valor', nota.valor).finish();
86
+ }
87
+ });
88
+ ```
89
+
90
+ Cada `startChild` vira uma barra no waterfall da aba de performance daquela execução no app — é como você responde "onde foram os 4 minutos". Sem nenhum span, a transaction ainda mede o tempo total da run; o waterfall só fica com uma barra.
91
+
92
+ - **Não chame `Hobots.startTransaction()` dentro de um handler.** Você criaria uma segunda transaction, sem correlação com a run, e ela não apareceria na execução.
93
+ - `ctx.tx` **não respeita o `tracesSampleRate`** — toda run gera uma transaction. A amostragem vale só para as transactions que você abre com `startTransaction()`.
94
+ - Span que você esquecer aberto é fechado no `finish()` da transaction, então o dado não se perde.
95
+ - Marque etapas com ressalva usando `span.setStatus('warning')` (ex.: retry que deu certo, resultado parcial): elas aparecem em âmbar no waterfall, distintas de sucesso e de falha (`'error'`).
96
+
67
97
  ## Cancelamento cooperativo
68
98
 
69
- Uma run cancelada no app **não mata** o seu handler — o SDK não interrompe código em execução. Ele apenas dispara `ctx.signal`. Enquanto o handler roda, o SDK mantém um **heartbeat de cancelamento** (a cada `cancelCheckIntervalMs`, default 5s, máx 15s): a run cancelada no app é detectada em segundos, mesmo que o handler nunca chame `ctx.log()`. Chame **`ctx.throwIfCanceled()`** em loops longos e pontos de parada (e troque esperas por `ctx.sleep()`):
99
+ Uma run cancelada no app **não mata** o seu handler — o SDK não interrompe código em execução. Ele apenas dispara `ctx.signal`. Enquanto o handler roda, o SDK mantém um **heartbeat de cancelamento** (a cada `cancelCheckIntervalMs`, veja [Tempos e intervalos](./api-reference.md#tempos-e-intervalos)): a run cancelada no app é detectada em segundos, mesmo que o handler nunca chame `ctx.log()`. Chame **`ctx.throwIfCanceled()`** em loops longos e pontos de parada (e troque esperas por `ctx.sleep()`):
70
100
 
71
101
  ```js
72
102
  Hobots.register('processar-nfe', async (params, ctx) => {
@@ -84,7 +114,7 @@ Quando o cancelamento veio do app, a run já está `cancelada` no servidor — o
84
114
  Se o processo do agente **morre no meio de uma run**, a solicitação vira **falha** no app com erro `AgentTerminated` ("execução encerrada inesperadamente") — sem re-execução automática. Dois caminhos garantem isso:
85
115
 
86
116
  - **Morte capturável** (SIGINT/SIGTERM sem handler próprio do programa, erro não tratado): o SDK reporta a falha na hora (~2s, um PATCH best-effort com timeout curto) e então deixa o processo morrer com a semântica default do sinal. Os handlers são instalados no `start()` e **nunca interferem** quando o programa tem handler próprio para o sinal (o shutdown gracioso via `close()` continua sendo o caminho normal). Desative com `manager: { installExitHandlers: false }`.
87
- - **Morte dura** (kill -9, queda de energia, rede fora): nada roda no cliente. O heartbeat de cancelamento é também o **sinal de vida da run** — o servidor marca a run como falha ao notar **~60s sem reporte**.
117
+ - **Morte dura** (kill -9, queda de energia, rede fora): nada roda no cliente. O heartbeat de cancelamento é também o **sinal de vida da run** — o servidor marca a run como falha ao notar silêncio prolongado (o [sweep](./api-reference.md#tempos-e-intervalos) server-side).
88
118
 
89
119
  `close(timeoutMs)` que estoura o timeout com runs ainda em execução também reporta essas runs como `AgentTerminated` — o processo vai morrer em seguida e o app não deve mostrar a run como "executando" para sempre.
90
120
 
@@ -94,7 +124,7 @@ Se o agente morrer logo **após capturar** uma run (antes de reportar `running`)
94
124
 
95
125
  ## Concorrência
96
126
 
97
- `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**: enquanto vêm runs, re-pesquisa imediatamente; quando não vem nada, espera `pollIntervalMs`.
127
+ `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](./concepts.md#drain-mode)**: enquanto vêm runs, re-pesquisa imediatamente; quando não vem nada, espera `pollIntervalMs`. Ou seja, o intervalo de poll é o teto da espera **quando não há trabalho** — não a latência típica para começar uma run.
98
128
 
99
129
  ## Timeout por run
100
130
 
@@ -104,4 +134,4 @@ Todas as opções em [Referência de API → ManagerOptions](./api-reference.md#
104
134
 
105
135
  ## Como o claim funciona (resumo)
106
136
 
107
- O SDK é um cliente HTTP: ele faz `POST /v1/manager/poll` e o **servidor** reivindica as runs pendentes de forma atômica com `FOR UPDATE SKIP LOCKED` — dois agentes nunca pegam a mesma run. Detalhes em [Protocolo & transporte](./protocol.md).
137
+ O SDK é um cliente HTTP: ele faz `POST /v1/manager/poll` e o **servidor** reivindica as runs pendentes de forma atômica com `FOR UPDATE SKIP LOCKED` — dois agentes nunca pegam a mesma run. Conceito em [Claim](./concepts.md#claim), detalhes em [Protocolo & transporte](./protocol.md).
package/docs/monitor.md CHANGED
@@ -6,7 +6,7 @@ Capability de telemetria: erros, performance, crons e uptime. Sempre ativa no `i
6
6
  import * as Hobots from 'hobots';
7
7
  ```
8
8
 
9
- Todas as funções abaixo são no-op seguros se `Hobots.init()` não foi chamado (com aviso no console). Assinaturas completas em [Referência de API](./api-reference.md).
9
+ Todas as funções abaixo são no-op seguros se `Hobots.init()` não foi chamado (com aviso no console). Assinaturas completas em [Referência de API](./api-reference.md); o que cada conceito **é**, em [Conceitos](./concepts.md).
10
10
 
11
11
  ## init
12
12
 
@@ -39,8 +39,12 @@ Hobots.captureMessage('Lote processado com sucesso', 'info');
39
39
 
40
40
  Ambas retornam o `event_id` (ou `undefined` se não inicializado). O segundo argumento é um [`CaptureHint`](./api-reference.md#capturehint) (`level`, `tags`, `extra`, `fingerprint`).
41
41
 
42
+ > **Atenção:** o `event_id` retornado **não é confirmação de envio** — se o evento foi descartado por amostragem ou por `beforeSend`, você recebe um id normalmente e nada foi enfileirado.
43
+
42
44
  Erros **não tratados** (`uncaughtException` / `unhandledRejection`) são capturados e flushados automaticamente antes do processo sair — desligue com `captureUnhandled: false`.
43
45
 
46
+ Conceitos: [Evento](./concepts.md#evento) · [Level](./concepts.md#level-severity) · [Fingerprint](./concepts.md#fingerprint).
47
+
44
48
  ## Breadcrumbs
45
49
 
46
50
  Trilha do que aconteceu antes do erro; enviada junto do próximo evento.
@@ -51,6 +55,8 @@ Hobots.addBreadcrumb({ category: 'nfe', message: 'Baixou XML da nota 123', level
51
55
 
52
56
  O buffer é limitado por `maxBreadcrumbs` (default 100, mais antigos descartados).
53
57
 
58
+ Dois pontos que costumam surpreender: a lista **não é limpa** ao enviar um evento (o próximo evento leva as mesmas migalhas de novo), e ela é **global** — para uma trilha que não vaze, adicione dentro de um `withScope`. Conceito: [Breadcrumb](./concepts.md#breadcrumb).
59
+
54
60
  ## Escopo: tags, usuário, contexto, extras
55
61
 
56
62
  Dados anexados a **todos** os eventos seguintes:
@@ -63,6 +69,14 @@ Hobots.setContext('vm', { cpu: 4, ram: '8GB' });
63
69
  Hobots.setExtra('ultimoLote', 128);
64
70
  ```
65
71
 
72
+ Qual usar para quê: **tag** é rótulo curto e indexável (você vai filtrar por isso), **context** é grupo nomeado de dados que andam juntos (você vai ler), **extra** é depósito livre para o resto. Detalhes e critérios em [Tag](./concepts.md#tag) · [Context](./concepts.md#context) · [Extra](./concepts.md#extra).
73
+
74
+ Três comportamentos que valem saber:
75
+
76
+ - Tags podem vir de quatro lugares e a última vence: `init({ tags })` → scope → evento → `hint` da chamada.
77
+ - `setContext(nome, null)` **apaga** o grupo. O SDK já injeta os grupos `runtime` e `os` em todo evento.
78
+ - `setExtra` não é indexado — se você quiser filtrar por aquele campo, ele deveria ser uma tag.
79
+
66
80
  Para um escopo **isolado** (sem vazar para o global), use `withScope`:
67
81
 
68
82
  ```js
@@ -72,8 +86,12 @@ Hobots.withScope((scope) => {
72
86
  });
73
87
  ```
74
88
 
89
+ > **Atenção:** `withScope` é **síncrono** — ele troca o scope global e restaura no fim do callback. Se você der `await` lá dentro, o scope isolado vale para qualquer código que capturar eventos enquanto aquele `await` está pendente. Em código concorrente (várias runs em paralelo), passe as tags no `hint`: `captureException(error, { tags: { lote: '128' } })`. Conceito: [withScope](./concepts.md#withscope).
90
+
75
91
  ## Performance: transactions e spans
76
92
 
93
+ Uma **transaction** é uma unidade de trabalho nomeada e medida ("quanto minha rotina demorou"); os **spans** são as etapas dentro dela ("onde o tempo foi gasto"). Conceitos: [Transaction](./concepts.md#transaction) · [Span](./concepts.md#span).
94
+
77
95
  ```js
78
96
  const tx = Hobots.startTransaction('processar-lote', 'rpa.job');
79
97
 
@@ -91,7 +109,12 @@ tx.finish(); // só aqui a transaction é enviada (respeitando tracesSampleRate)
91
109
 
92
110
  - `startChild(op?, description?)` existe tanto em `Transaction` quanto em `Span` (aninhamento arbitrário).
93
111
  - Métodos encadeáveis: `setStatus`, `setData` (span), `setTag`/`setName` (transaction).
94
- - `finish()` na transaction fecha spans abertos e envia o payload; nada é enviado se ela não foi amostrada.
112
+ - Status de span: `'ok'` (default), `'warning'` (atenção, não é falha) ou `'error'` — cores distintas no waterfall.
113
+ - `finish()` na transaction fecha spans abertos e envia o payload; nada é enviado se ela não foi amostrada. É **idempotente** — chamar duas vezes não envia duas.
114
+ - A decisão de amostragem acontece no **`startTransaction()`**, não no `finish()`. Esquecer o `finish()` de um span não perde o dado: ele é fechado no instante em que a transaction fecha.
115
+ - Uma transaction leva as tags do `init()` e as que você marcar com `tx.setTag()` — ela **não** herda as tags do scope.
116
+
117
+ Dentro de um handler do Manager não abra transaction: já existe uma em [`ctx.tx`](./concepts.md#transaction-de-run-ctxtx).
95
118
 
96
119
  ## Crons (check-ins)
97
120
 
@@ -113,6 +136,8 @@ const id = Hobots.checkIn({ monitorSlug: 'sync-notas', status: 'in_progress' });
113
136
  Hobots.checkIn({ monitorSlug: 'sync-notas', status: 'ok', checkInId: id, durationMs: 1234 });
114
137
  ```
115
138
 
139
+ `withMonitor` **relança** a exceção depois de fechar o check-in como `error` e capturar o evento (com a tag `monitor: <slug>`) — o seu `try/catch` em volta continua funcionando igual. Conceito: [Check-in](./concepts.md#check-in).
140
+
116
141
  ## Heartbeat (uptime)
117
142
 
118
143
  Bate "estou vivo" periodicamente; o servidor acusa o robô **offline** quando os batimentos param.
@@ -124,15 +149,25 @@ Hobots.init({
124
149
  });
125
150
  ```
126
151
 
127
- `heartbeat: true` usa os defaults. A identidade é o `instanceId` do `init()`. Campos em [HeartbeatOptions](./api-reference.md#heartbeatoptions).
152
+ `heartbeat: true` usa os defaults. A identidade é o `instanceId` do `init()`. Campos em [HeartbeatOptions](./api-reference.md#heartbeatoptions); conceito em [Heartbeat](./concepts.md#heartbeat).
153
+
154
+ O primeiro batimento sai **na hora** em que o `init()` roda — não depois do primeiro intervalo. O batimento também carrega o intervalo configurado: é assim que o servidor sabe quanto silêncio é normal antes de declarar o robô offline.
128
155
 
129
156
  Essa identidade é a mesma que o Manager envia no poll (`agent.instance_id`) — os dois canais derivam do `instanceId` do `init()`, então o robô aparece como **um agente só** em Monitor → Agentes automaticamente.
130
157
 
131
158
  ## Flush e close
132
159
 
160
+ Nada é enviado na hora: cada evento, transaction, check-in e heartbeat entra numa **fila em memória** e sai em lote. É por isso que essas duas funções existem — quando o processo morre, a fila morre com ele. Conceito: [Fila, flush e close](./concepts.md#fila-flush-e-close).
161
+
133
162
  ```js
134
163
  await Hobots.flush(2000); // envia o que está em buffer, mantém o SDK vivo
135
164
  await Hobots.close(2000); // para o Manager (se ativo) + heartbeat + flush final do transport
136
165
  ```
137
166
 
167
+ O `timeoutMs` é opcional nas duas — o default é 5s (veja [Tempos e intervalos](./api-reference.md#tempos-e-intervalos)). As duas devolvem `false` quando estouram o prazo, o que significa que o SDK parou de esperar, **não** que os dados foram perdidos: os envios em voo continuam tentando enquanto o processo existir.
168
+
138
169
  Chame `close` antes do processo sair. Os timers do Monitor são `unref`'d, então a telemetria **não** segura o processo vivo sozinha (só o `start()` do Manager segura).
170
+
171
+ > **Atenção:** `close()` é **terminal**. Depois dele, todo evento, transaction, check-in ou heartbeat novo é **descartado** com aviso no console — não existe `reopen()`. Se o programa precisar voltar a emitir telemetria, chame `Hobots.init()` de novo.
172
+
173
+ > **Atenção:** o `timeoutMs` do `close()` é gasto **duas vezes, em sequência** — uma esperando as runs do Manager, outra no flush final. `close(15_000)` pode levar até ~30s no pior caso. Dimensione por aí se o seu SIGTERM tem prazo curto.
package/docs/protocol.md CHANGED
@@ -40,7 +40,9 @@ Endpoint fixo `POST /v1/ingest` recebe **envelopes** JSON autenticados por Beare
40
40
 
41
41
  Tipos de item: `event`, `transaction`, `check_in`, `heartbeat` (definidos em `hobots/protocol`).
42
42
 
43
- **Flush & retries:** o transport flusha por `flushIntervalMs` (default 2s) ou ao atingir `maxBatchSize` (default 10). Ladder de retry `[1s, 4s, 16s]`; respostas 4xx (exceto 429) são descartadas sem retry; a fila tem teto de 200 itens (descarta os mais antigos).
43
+ Um `transaction` pode carregar `run_id`: é assim que a [transaction de uma run do Manager](./concepts.md#transaction-de-run-ctxtx) (`ctx.tx`) é correlacionada à execução, e o que permite o app montar o waterfall de performance dentro da solicitação. Transactions abertas com `startTransaction()` não têm esse campo.
44
+
45
+ **Flush & retries:** o transport flusha por `flushIntervalMs` ou ao atingir `maxBatchSize`. Ladder de retry de 3 tentativas; respostas 4xx (exceto 429) são descartadas sem retry; a fila tem teto fixo e descarta os itens **mais antigos** ao encher. Todos os valores em [Tempos e intervalos](./api-reference.md#tempos-e-intervalos).
44
46
 
45
47
  ## Manager — polling e reporte
46
48
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hobots",
3
- "version": "1.3.5",
3
+ "version": "1.5.0",
4
4
  "description": "SDK JavaScript da Hobots — captura de erros, transactions, check-ins de cron e heartbeats (Monitor) e execução remota de tarefas sob demanda (Manager) para RPAs e integrações. Um único import e um único init().",
5
5
  "type": "module",
6
6
  "sideEffects": false,