hobots 1.5.1 → 1.7.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/README.md +46 -74
- package/dist/index.cjs +267 -144
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -123
- package/dist/index.d.ts +31 -123
- package/dist/index.js +267 -144
- package/dist/index.js.map +1 -1
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.cts +73 -4
- package/dist/protocol.d.ts +73 -4
- package/docs/README.md +8 -8
- package/docs/api-reference.md +40 -44
- package/docs/concepts.md +67 -52
- package/docs/getting-started.md +17 -17
- package/docs/monitor.md +8 -8
- package/docs/protocol.md +21 -25
- package/docs/{manager.md → tasks.md} +18 -18
- package/package.json +3 -7
- package/dist/manager/protocol.cjs +0 -19
- package/dist/manager/protocol.cjs.map +0 -1
- package/dist/manager/protocol.d.cts +0 -72
- package/dist/manager/protocol.d.ts +0 -72
- package/dist/manager/protocol.js +0 -1
- package/dist/manager/protocol.js.map +0 -1
package/docs/concepts.md
CHANGED
|
@@ -8,42 +8,55 @@ Os defaults numéricos **não** estão aqui — eles vivem em [Tempos e interval
|
|
|
8
8
|
|
|
9
9
|
## Mapa rápido
|
|
10
10
|
|
|
11
|
-
| Conceito
|
|
12
|
-
|
|
|
13
|
-
| [Evento](#evento)
|
|
14
|
-
| [Level](#level-severity)
|
|
15
|
-
| [Fingerprint](#fingerprint)
|
|
16
|
-
| [Scope](#scope)
|
|
17
|
-
| [withScope](#withscope)
|
|
18
|
-
| [Tag](#tag)
|
|
19
|
-
| [Context](#context)
|
|
20
|
-
| [Extra](#extra)
|
|
21
|
-
| [Breadcrumb](#breadcrumb)
|
|
22
|
-
| [Transaction](#transaction)
|
|
23
|
-
| [Span](#span)
|
|
24
|
-
| [ctx.tx](#transaction-de-run-ctxtx)
|
|
25
|
-
| [Amostragem](#amostragem-samplerate-e-tracessamplerate) | Enviar só uma fração do que acontece
|
|
26
|
-
| [Check-in](#check-in)
|
|
27
|
-
| [Heartbeat](#heartbeat)
|
|
28
|
-
| [instanceId](#instanceid)
|
|
29
|
-
| [Release e environment](#release-e-environment)
|
|
30
|
-
| [Run](#run)
|
|
31
|
-
| [Claim](#claim)
|
|
32
|
-
| [Drain mode](#drain-mode)
|
|
33
|
-
| [Fila, flush e close](#fila-flush-e-close)
|
|
34
|
-
| [beforeSend](#beforesend)
|
|
11
|
+
| Conceito | Em uma frase | Quando você usa |
|
|
12
|
+
| ------------------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------- |
|
|
13
|
+
| [Evento](#evento) | Uma foto de algo que aconteceu — um erro ou uma mensagem | `captureException` / `captureMessage` |
|
|
14
|
+
| [Level](#level-severity) | A gravidade do evento | Para separar o que acorda alguém do que é só registro |
|
|
15
|
+
| [Fingerprint](#fingerprint) | A chave de agrupamento dos eventos em issues | Quando o agrupamento automático erra |
|
|
16
|
+
| [Scope](#scope) | A mochila de contexto que viaja com todo evento | Você raramente mexe direto; mexe pelos `set*` |
|
|
17
|
+
| [withScope](#withscope) | Um scope temporário e isolado | Contexto que **não** deve vazar para o resto do programa |
|
|
18
|
+
| [Tag](#tag) | Rótulo curto e indexável (`string` → `string`) | Você vai **filtrar** ou **agrupar** por isso |
|
|
19
|
+
| [Context](#context) | Grupo nomeado de dados estruturados | Você vai **ler** isso ao abrir o evento |
|
|
20
|
+
| [Extra](#extra) | Depósito livre, sem estrutura | Não é filtro nem grupo coerente — é anexo |
|
|
21
|
+
| [Breadcrumb](#breadcrumb) | Migalha da trilha que levou até o erro | Marcar cada passo relevante do robô |
|
|
22
|
+
| [Transaction](#transaction) | Unidade de trabalho nomeada, com duração | Medir quanto uma rotina inteira demorou |
|
|
23
|
+
| [Span](#span) | Uma etapa medida dentro da transaction | Descobrir **onde** o tempo foi gasto |
|
|
24
|
+
| [ctx.tx](#transaction-de-run-ctxtx) | A transaction que o SDK já abriu para a run | Medir as etapas de um handler de task |
|
|
25
|
+
| [Amostragem](#amostragem-samplerate-e-tracessamplerate) | Enviar só uma fração do que acontece | Volume alto demais para enviar tudo |
|
|
26
|
+
| [Check-in](#check-in) | Par de avisos "comecei" / "terminei" de um job agendado | Cron que precisa ser vigiado |
|
|
27
|
+
| [Heartbeat](#heartbeat) | Batimento "estou vivo" | Saber que o robô está de pé, mesmo parado |
|
|
28
|
+
| [instanceId](#instanceid) | A identidade da instância do robô | Sempre — é obrigatório no `init()` |
|
|
29
|
+
| [Release e environment](#release-e-environment) | Qual versão, em qual ambiente | Separar produção de homologação; achar a versão que quebrou |
|
|
30
|
+
| [Run](#run) | Uma execução solicitada no app | As tasks inteiras giram em volta disso |
|
|
31
|
+
| [Claim](#claim) | O ato de um agente pegar uma run para si | Entender por que dois agentes nunca colidem |
|
|
32
|
+
| [Drain mode](#drain-mode) | O agente acelera o poll enquanto tem trabalho | Entender o ritmo do polling |
|
|
33
|
+
| [Fila, flush e close](#fila-flush-e-close) | Nada é enviado na hora — vai para uma fila | Encerrar o processo sem perder telemetria |
|
|
34
|
+
| [beforeSend](#beforesend) | O último gancho antes do envio | Censurar ou descartar eventos |
|
|
35
35
|
|
|
36
36
|
> **Nota de manutenção:** esta página é a fonte canônica das explicações de conceito. As outras páginas da doc, o `README.md` do pacote e os tooltips do app parafraseiam daqui — quando um conceito muda, muda aqui primeiro.
|
|
37
37
|
|
|
38
|
+
## O SDK e os módulos do app
|
|
39
|
+
|
|
40
|
+
Cada API do SDK alimenta um módulo do app da Hobots (app.hobots.app). O mapa:
|
|
41
|
+
|
|
42
|
+
| No SDK | No app |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| **Projeto** — o "funcionário digital"; dono do Client Secret (`hb_…`) que autentica tudo | **Configurações → Projetos** (é lá que você copia a credencial) |
|
|
45
|
+
| **Agente / instância** — o par `(project_id, instance_id)`; heartbeat e poll de tasks convergem nele | Módulo **Agentes** (uptime, performance) |
|
|
46
|
+
| **[Evento](#evento)** agrupado em **issue** — `captureException` / `captureMessage` | Módulo **Issues** |
|
|
47
|
+
| **Monitor (cron)** — `withMonitor` / `checkIn` com `schedule` | Módulo **Monitores** |
|
|
48
|
+
| **[Transaction](#transaction) / spans** — `startTransaction` (e [`ctx.tx`](#transaction-de-run-ctxtx) dentro de uma run) | Performance no módulo **Agentes**; nas execuções, a aba de performance da solicitação |
|
|
49
|
+
| **Task / [run](#run)** — `register('task-slug', …)` casa com o `task_slug` do formulário | Módulo **Solicitações** (formulários em **Configurações → Formulários**) |
|
|
50
|
+
|
|
38
51
|
---
|
|
39
52
|
|
|
40
53
|
## Evento
|
|
41
54
|
|
|
42
|
-
Um **evento** é a foto de um instante que vale a pena registrar: uma exceção que estourou ou uma mensagem que você mandou. É a unidade básica
|
|
55
|
+
Um **evento** é a foto de um instante que vale a pena registrar: uma exceção que estourou ou uma mensagem que você mandou. É a unidade básica da telemetria.
|
|
43
56
|
|
|
44
57
|
Quando você chama `captureException` ou `captureMessage`, o SDK monta o evento com o que a exceção traz (tipo, mensagem, stack trace com os frames em ordem) e **anexa tudo que está no [scope](#scope)** naquele momento: tags, usuário, contexts, extras e a trilha de [breadcrumbs](#breadcrumb). Depois disso o evento vai para a [fila](#fila-flush-e-close) e é enviado em lote.
|
|
45
58
|
|
|
46
|
-
No servidor, eventos parecidos são agrupados numa **issue** —
|
|
59
|
+
No servidor, eventos parecidos são agrupados numa **issue** — o que você vê no módulo Issues do app. É por isso que 4.000 falhas do mesmo timeout não viram 4.000 linhas na sua tela. Quem decide o que é "parecido" é o [fingerprint](#fingerprint).
|
|
47
60
|
|
|
48
61
|
```js
|
|
49
62
|
Hobots.captureException(error);
|
|
@@ -112,7 +125,7 @@ Dois pontos importantes:
|
|
|
112
125
|
`withScope` executa um callback com uma **cópia isolada** do scope. O que você escrever lá dentro vale para os eventos capturados dentro do callback e **não vaza** para fora.
|
|
113
126
|
|
|
114
127
|
```js
|
|
115
|
-
Hobots.withScope(
|
|
128
|
+
Hobots.withScope(scope => {
|
|
116
129
|
scope.setTag('lote', '128').setFingerprint(['reprocessamento']);
|
|
117
130
|
Hobots.captureMessage('Reprocessando lote 128');
|
|
118
131
|
});
|
|
@@ -172,11 +185,11 @@ Hobots.setExtra('tentativas', [1, 2, 3]);
|
|
|
172
185
|
|
|
173
186
|
É deliberadamente o menos estruturado dos três, e por isso o critério de escolha é simples:
|
|
174
187
|
|
|
175
|
-
| Você vai…
|
|
176
|
-
|
|
|
177
|
-
| filtrar, agrupar, contar por isso
|
|
178
|
-
| ler isso como um conjunto coerente
|
|
179
|
-
| nenhum dos dois — é só um dado a mais | **extra**
|
|
188
|
+
| Você vai… | Use |
|
|
189
|
+
| ------------------------------------- | ------------------- |
|
|
190
|
+
| filtrar, agrupar, contar por isso | [tag](#tag) |
|
|
191
|
+
| ler isso como um conjunto coerente | [context](#context) |
|
|
192
|
+
| nenhum dos dois — é só um dado a mais | **extra** |
|
|
180
193
|
|
|
181
194
|
Extra não é indexado: não dá para filtrar por ele nem construir gráfico com ele. Se você se pegar querendo isso, o campo era uma tag.
|
|
182
195
|
|
|
@@ -207,7 +220,7 @@ Tamanho do buffer: [`maxBreadcrumbs`](./api-reference.md#initoptions).
|
|
|
207
220
|
|
|
208
221
|
Uma **transaction** é uma unidade de trabalho **nomeada** e **medida**: ela começa, termina, e o que fica registrado é quanto tempo levou. É a resposta para "quanto demora minha rotina de emissão?" — e, quando você adiciona [spans](#span), para "onde foram esses minutos?".
|
|
209
222
|
|
|
210
|
-
Enquanto o [evento](#evento) responde
|
|
223
|
+
Enquanto o [evento](#evento) responde _o que quebrou_, a transaction responde _quanto tempo levou_. São os dois eixos da telemetria.
|
|
211
224
|
|
|
212
225
|
```js
|
|
213
226
|
const tx = Hobots.startTransaction('processar-lote', 'rpa.job');
|
|
@@ -226,7 +239,7 @@ Três coisas que valem saber:
|
|
|
226
239
|
|
|
227
240
|
O `status` default é `'ok'`; encadeáveis: `setStatus`, `setName`, `setTag`.
|
|
228
241
|
|
|
229
|
-
Dentro de um handler
|
|
242
|
+
Dentro de um handler de task você **não** cria transaction — já existe uma em [`ctx.tx`](#transaction-de-run-ctxtx), e o `startTransaction()` chamado lá dentro devolve exatamente ela.
|
|
230
243
|
|
|
231
244
|
## Span
|
|
232
245
|
|
|
@@ -249,7 +262,7 @@ O `op` é a categoria (útil para agrupar "todas as queries") e a `description`
|
|
|
249
262
|
|
|
250
263
|
## Transaction de run (ctx.tx)
|
|
251
264
|
|
|
252
|
-
Dentro de um handler
|
|
265
|
+
Dentro de um handler de task você **não precisa** criar uma transaction: o SDK já abriu uma para aquela execução e entregou em `ctx.tx`. Ela nasce quando o agente captura a run, é **correlacionada** à execução pelo `run_id`, recebe o status conforme o desfecho (`ok`, `error` ou `cancelled`) e é **finalizada automaticamente** quando o handler termina — você nunca chama `ctx.tx.finish()`.
|
|
253
266
|
|
|
254
267
|
O que sobra para você é a parte interessante: marcar as **etapas** do trabalho com [spans](#span). Cada `startChild` vira uma barra no waterfall da aba de performance daquela execução no app.
|
|
255
268
|
|
|
@@ -270,9 +283,9 @@ Hobots.register('processar-nfe', async (params, ctx) => {
|
|
|
270
283
|
|
|
271
284
|
Sem nenhum `startChild` a transaction ainda existe e ainda mede o tempo total da run — o waterfall só fica com uma barra. Cada span que você abre é uma resposta a mais para "onde foram os 4 minutos".
|
|
272
285
|
|
|
273
|
-
-
|
|
274
|
-
- `ctx.tx` **não respeita o `tracesSampleRate`** — toda run gera uma transaction. A amostragem vale para as transactions que **você** abre com `startTransaction()
|
|
275
|
-
- O nome é `run:<task>` e o `op` é `
|
|
286
|
+
- **`Hobots.startTransaction()` dentro de um handler devolve a própria `ctx.tx`.** Cada execução tem exatamente **uma** transaction: o SDK detecta o contexto da run e reutiliza a transaction dela em vez de criar uma segunda — os spans caem no mesmo waterfall. Fora de um handler, `startTransaction()` cria uma transaction nova, sem `run_id`.
|
|
287
|
+
- `ctx.tx` **não respeita o `tracesSampleRate`** — toda run gera uma transaction. A amostragem vale para as transactions que **você** abre com `startTransaction()` fora de uma run.
|
|
288
|
+
- O nome é `run:<task>` e o `op` é `hobots.task`, preenchidos pelo SDK.
|
|
276
289
|
|
|
277
290
|
## Amostragem (sampleRate e tracesSampleRate)
|
|
278
291
|
|
|
@@ -280,9 +293,9 @@ Sem nenhum `startChild` a transaction ainda existe e ainda mede o tempo total da
|
|
|
280
293
|
|
|
281
294
|
São dois controles independentes, os dois de `0` a `1`:
|
|
282
295
|
|
|
283
|
-
| Opção
|
|
284
|
-
|
|
|
285
|
-
| `sampleRate`
|
|
296
|
+
| Opção | Afeta | `0.1` significa |
|
|
297
|
+
| ------------------ | ------------------------------------------------------------- | ---------------------------------- |
|
|
298
|
+
| `sampleRate` | [eventos](#evento) (`captureException`, `captureMessage`) | ~10% dos erros são enviados |
|
|
286
299
|
| `tracesSampleRate` | [transactions](#transaction) abertas por `startTransaction()` | ~10% das transactions são enviadas |
|
|
287
300
|
|
|
288
301
|
O sorteio é por item, no momento em que ele é criado — nunca "1 a cada 10", e sim 10% de chance cada.
|
|
@@ -319,7 +332,7 @@ Detalhes que importam:
|
|
|
319
332
|
|
|
320
333
|
O **heartbeat** é um batimento periódico dizendo "estou vivo". O servidor conhece a cadência esperada e marca a instância como **offline** quando os batimentos param.
|
|
321
334
|
|
|
322
|
-
É o complemento do [check-in](#check-in): check-in vigia
|
|
335
|
+
É o complemento do [check-in](#check-in): check-in vigia _execuções agendadas_, heartbeat vigia _o processo_. Um robô que fica de plantão esperando trabalho não tem cron para monitorar — mas você ainda quer saber quando a VM dele caiu.
|
|
323
336
|
|
|
324
337
|
```js
|
|
325
338
|
Hobots.init({
|
|
@@ -338,11 +351,13 @@ Dois detalhes:
|
|
|
338
351
|
|
|
339
352
|
O timer do heartbeat é `unref`'d — ele **não** segura o processo vivo.
|
|
340
353
|
|
|
354
|
+
Com `tasks: true`, o heartbeat é **opcional**: o próprio poll de tasks já marca a presença do agente no painel. Ele continua essencial para agentes só de telemetria.
|
|
355
|
+
|
|
341
356
|
## instanceId
|
|
342
357
|
|
|
343
358
|
O **`instanceId`** é a identidade da instância do robô: qual VM, qual container, qual máquina. É **obrigatório** no `init()` e deve ser **estável entre reinícios** (`vm-cliente-01`, `container-nfe-3`) — se você gerar um valor novo a cada boot, o app vê uma frota de robôs fantasmas em vez de um robô que reiniciou.
|
|
344
359
|
|
|
345
|
-
O que o torna especial é que ele é a identidade dos **dois** canais ao mesmo tempo: o [heartbeat](#heartbeat)
|
|
360
|
+
O que o torna especial é que ele é a identidade dos **dois** canais ao mesmo tempo: o [heartbeat](#heartbeat) e o agente de [tasks](./tasks.md) enviam o mesmo `instanceId`. O servidor correlaciona os dois, e o robô aparece como **um agente só** no módulo Agentes — sem nenhuma configuração extra.
|
|
346
361
|
|
|
347
362
|
## Release e environment
|
|
348
363
|
|
|
@@ -350,8 +365,8 @@ O que o torna especial é que ele é a identidade dos **dois** canais ao mesmo t
|
|
|
350
365
|
|
|
351
366
|
Servem a duas perguntas concretas:
|
|
352
367
|
|
|
353
|
-
-
|
|
354
|
-
-
|
|
368
|
+
- _"quebrou desde quando?"_ — com `release`, você compara o antes e o depois de um deploy e sabe qual versão introduziu o erro.
|
|
369
|
+
- _"isso é real ou é teste?"_ — com `environment`, o ruído da homologação não polui os alertas de produção.
|
|
355
370
|
|
|
356
371
|
`environment` default é `production`. Se você roda homologação sem definir isso, os dois ambientes se misturam na mesma issue — vale configurar no primeiro dia.
|
|
357
372
|
|
|
@@ -359,7 +374,7 @@ Servem a duas perguntas concretas:
|
|
|
359
374
|
|
|
360
375
|
Uma **run** (execução) é um pedido concreto de trabalho criado no app: alguém preencheu o formulário da task, ou uma automação disparou, e agora existe uma solicitação esperando um agente pegar.
|
|
361
376
|
|
|
362
|
-
É a unidade em volta da qual
|
|
377
|
+
É a unidade em volta da qual as [tasks](./tasks.md) inteiras giram — no app, cada run é uma solicitação no módulo **Solicitações**. O ciclo de vida no servidor:
|
|
363
378
|
|
|
364
379
|
```
|
|
365
380
|
pending → claimed → running → succeeded | failed | canceled
|
|
@@ -372,7 +387,7 @@ pending → claimed → running → succeeded | failed | canceled
|
|
|
372
387
|
|
|
373
388
|
O agente só transiciona para `running`, `succeeded` e `failed`. `pending` e `claimed` são do servidor; `canceled` vem de alguém cancelando no app.
|
|
374
389
|
|
|
375
|
-
Cada run carrega os **`params`** (o JSON validado do formulário) e chega ao seu código como o primeiro argumento do handler, junto de um [`RunContext`](./api-reference.md#
|
|
390
|
+
Cada run carrega os **`params`** (o JSON validado do formulário) e chega ao seu código como o primeiro argumento do handler, junto de um [`RunContext`](./api-reference.md#taskhandler--runcontext) com logs, sinal de cancelamento e a [transaction](#transaction-de-run-ctxtx).
|
|
376
391
|
|
|
377
392
|
## Claim
|
|
378
393
|
|
|
@@ -406,13 +421,13 @@ Devolve `true` se tudo saiu dentro do tempo e `false` se estourou. `false` **nã
|
|
|
406
421
|
|
|
407
422
|
### `close(timeoutMs?)` — "acabou, encerra"
|
|
408
423
|
|
|
409
|
-
Encerra tudo, **nesta ordem**: para o agente
|
|
424
|
+
Encerra tudo, **nesta ordem**: para o agente de tasks (aguardando as runs em execução) → para o heartbeat → faz o flush final e fecha o transport. É sempre a última linha do seu programa.
|
|
410
425
|
|
|
411
426
|
> **Atenção:** `close()` é **terminal e sem volta**. Depois dele, todo `captureException`, transaction, check-in ou heartbeat é **descartado** com um aviso no console — não existe `reopen()`. Se o programa precisar voltar a emitir telemetria, chame `Hobots.init()` de novo.
|
|
412
427
|
|
|
413
|
-
> **Atenção:** o `timeoutMs` do `close()` é aplicado **duas vezes, em sequência** — uma para esperar as runs
|
|
428
|
+
> **Atenção:** o `timeoutMs` do `close()` é aplicado **duas vezes, em sequência** — uma para esperar as runs de tasks, outra para o flush final. `close(15_000)` pode levar até ~30s no pior caso. Se você tem um SIGTERM com prazo curto (Kubernetes, systemd), dimensione por aí.
|
|
414
429
|
|
|
415
|
-
O `flush` cobre a fila
|
|
430
|
+
O `flush` cobre a fila da **telemetria**. Os logs de uma run de task (`ctx.log`) são um canal separado, com o próprio ritmo — quem espera por eles é o `close()`/`stop()`, ao aguardar a run terminar.
|
|
416
431
|
|
|
417
432
|
Defaults e tempos dos dois: [Tempos e intervalos](./api-reference.md#tempos-e-intervalos).
|
|
418
433
|
|
|
@@ -424,9 +439,9 @@ Defaults e tempos dos dois: [Tempos e intervalos](./api-reference.md#tempos-e-in
|
|
|
424
439
|
Hobots.init({
|
|
425
440
|
clientSecret,
|
|
426
441
|
instanceId: 'vm-cliente-01',
|
|
427
|
-
beforeSend:
|
|
442
|
+
beforeSend: event => {
|
|
428
443
|
if (event.exception?.type === 'AbortError') return null; // não me interessa
|
|
429
|
-
delete event.extra?.senha;
|
|
444
|
+
delete event.extra?.senha; // censura
|
|
430
445
|
return event;
|
|
431
446
|
},
|
|
432
447
|
});
|
package/docs/getting-started.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
pnpm add hobots
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Sem dependências de runtime. Requer **Node 18+** (usa o `fetch` global).
|
|
9
|
+
Sem dependências de runtime. Requer **Node 18+** (usa o `fetch` global). A telemetria também funciona no browser para captura de erros.
|
|
10
10
|
|
|
11
11
|
## Conceitos em 2 minutos
|
|
12
12
|
|
|
@@ -23,13 +23,13 @@ Os termos que aparecem nesta página, em uma frase cada. Explicação completa d
|
|
|
23
23
|
|
|
24
24
|
## O Client Secret
|
|
25
25
|
|
|
26
|
-
Cada projeto tem **um único Client Secret** — a mesma credencial autentica
|
|
26
|
+
Cada projeto tem **um único Client Secret** — a mesma credencial autentica a telemetria **e** as tasks:
|
|
27
27
|
|
|
28
28
|
```
|
|
29
29
|
hb_<64 hex>
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
A credencial é **secreta** e viaja sempre no header `Authorization: Bearer` — nunca na URL. Copie o Client Secret no app, em **Configurações → Projetos** — os snippets de integração já vêm prontos.
|
|
33
33
|
|
|
34
34
|
O SDK fala com a API oficial `https://api2.hobots.app` automaticamente — não há configuração de host no `init()`. Em desenvolvimento, aponte para outro host com a env `HOBOTS_API_URL`:
|
|
35
35
|
|
|
@@ -39,13 +39,13 @@ HOBOTS_API_URL=http://localhost:3001 node meu-robo.js
|
|
|
39
39
|
|
|
40
40
|
Veja [Protocolo & transporte](./protocol.md) para os endpoints derivados da URL base.
|
|
41
41
|
|
|
42
|
-
##
|
|
42
|
+
## Telemetria e tasks — qual usar
|
|
43
43
|
|
|
44
44
|
As duas capabilities vivem no mesmo `init()` e você usa **uma, outra ou as duas**:
|
|
45
45
|
|
|
46
46
|
- Quer só saber se o robô quebrou, quanto demora e se está vivo? → `init({ clientSecret, heartbeat: true })`.
|
|
47
|
-
- Quer disparar o robô sob demanda a partir de um formulário no app? → `init({ clientSecret,
|
|
48
|
-
- Ambos? Passe `heartbeat` e `
|
|
47
|
+
- Quer disparar o robô sob demanda a partir de um formulário no app? → `init({ clientSecret, tasks: true })` + `register()` + `start()`.
|
|
48
|
+
- Ambos? Passe `heartbeat` e `tasks` no **mesmo `init()`**. É o cenário do
|
|
49
49
|
[`full.ts`](../../hobots-sdk-demos/src/full.ts) nos demos do SDK.
|
|
50
50
|
|
|
51
51
|
## O `init` único
|
|
@@ -57,7 +57,7 @@ Hobots.init({
|
|
|
57
57
|
clientSecret: 'hb_<64 hex>',
|
|
58
58
|
environment: 'production',
|
|
59
59
|
release: 'robo-nfe@1.4.2',
|
|
60
|
-
instanceId: 'vm-cliente-01', // identidade única: heartbeat + agente
|
|
60
|
+
instanceId: 'vm-cliente-01', // identidade única: heartbeat + agente de tasks
|
|
61
61
|
heartbeat: true, // "estou vivo" a cada 30s
|
|
62
62
|
});
|
|
63
63
|
|
|
@@ -71,11 +71,11 @@ try {
|
|
|
71
71
|
}
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
Chame `Hobots.init` **uma vez**, o mais cedo possível no processo. Detalhes em [
|
|
74
|
+
Chame `Hobots.init` **uma vez**, o mais cedo possível no processo. Detalhes em [Telemetria](./monitor.md).
|
|
75
75
|
|
|
76
|
-
## Primeiro agente (
|
|
76
|
+
## Primeiro agente (tasks)
|
|
77
77
|
|
|
78
|
-
Habilite a execução sob demanda com o campo `
|
|
78
|
+
Habilite a execução sob demanda com o campo `tasks` no mesmo `init()`:
|
|
79
79
|
|
|
80
80
|
```js
|
|
81
81
|
import * as Hobots from 'hobots';
|
|
@@ -83,7 +83,7 @@ import * as Hobots from 'hobots';
|
|
|
83
83
|
Hobots.init({
|
|
84
84
|
clientSecret: 'hb_<64 hex>',
|
|
85
85
|
instanceId: 'vm-cliente-01',
|
|
86
|
-
|
|
86
|
+
tasks: true, // ou { pollIntervalMs, concurrency, ... }
|
|
87
87
|
});
|
|
88
88
|
|
|
89
89
|
Hobots.register('processar-nfe', async (params, ctx) => {
|
|
@@ -93,21 +93,21 @@ Hobots.register('processar-nfe', async (params, ctx) => {
|
|
|
93
93
|
Hobots.start(); // mantém o processo vivo até stop()/close()
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
-
> O `instanceId` é a identidade dos **dois** canais (heartbeat
|
|
96
|
+
> O `instanceId` é a identidade dos **dois** canais (heartbeat e poll de tasks) — o robô aparece como **um agente só** no módulo Agentes do app, sem configuração extra. Com `tasks: true`, o heartbeat é opcional: o próprio poll já marca a presença do agente.
|
|
97
97
|
|
|
98
|
-
Detalhes (cancelamento, concorrência, idempotência) em [
|
|
98
|
+
As execuções aparecem no módulo **Solicitações** do app; o `task` do `register()` é o `task_slug` do formulário em **Configurações → Formulários**. Detalhes (cancelamento, concorrência, idempotência) em [Tasks](./tasks.md).
|
|
99
99
|
|
|
100
100
|
## Encerramento
|
|
101
101
|
|
|
102
|
-
`close()` encerra tudo na ordem certa: para o
|
|
102
|
+
`close()` encerra tudo na ordem certa: para o polling de tasks (aguardando a run atual), o heartbeat e faz o flush final do transport.
|
|
103
103
|
|
|
104
|
-
Sem
|
|
104
|
+
Sem `start()` chamado, o SDK não segura o processo (timers `unref`'d), mas faça o flush final para não perder eventos em buffer:
|
|
105
105
|
|
|
106
106
|
```js
|
|
107
107
|
await Hobots.close(2000);
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
-
Com
|
|
110
|
+
Com as tasks, `start()` segura o processo por design. Encerre de forma graciosa aguardando a run atual:
|
|
111
111
|
|
|
112
112
|
```js
|
|
113
113
|
process.on('SIGINT', () => {
|
|
@@ -115,6 +115,6 @@ process.on('SIGINT', () => {
|
|
|
115
115
|
});
|
|
116
116
|
```
|
|
117
117
|
|
|
118
|
-
> **Atenção:** o `timeoutMs` do `close()` é gasto **duas vezes, em sequência** — uma esperando as runs
|
|
118
|
+
> **Atenção:** o `timeoutMs` do `close()` é gasto **duas vezes, em sequência** — uma esperando as runs de tasks, outra no flush final. O `close(15_000)` acima pode levar até ~30s no pior caso. Se o seu SIGTERM tem prazo curto (Kubernetes, systemd), dimensione por aí. Os defaults, quando você omite o argumento, estão em [Tempos e intervalos](./api-reference.md#tempos-e-intervalos).
|
|
119
119
|
|
|
120
120
|
Para parar de aceitar execuções **sem** derrubar a telemetria, use `Hobots.stop(timeoutMs?)`. Conceito das duas funções em [Fila, flush e close](./concepts.md#fila-flush-e-close).
|
package/docs/monitor.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Telemetria
|
|
2
2
|
|
|
3
|
-
Capability de telemetria: erros, performance, crons
|
|
3
|
+
Capability de telemetria: erros (módulo **Issues**), performance e uptime (módulo **Agentes**), crons (módulo **Monitores**). Sempre ativa no `init()` único:
|
|
4
4
|
|
|
5
5
|
```js
|
|
6
6
|
import * as Hobots from 'hobots';
|
|
@@ -114,11 +114,11 @@ tx.finish(); // só aqui a transaction é enviada (respeitando tracesSampleRate)
|
|
|
114
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
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
116
|
|
|
117
|
-
Dentro de um handler
|
|
117
|
+
Dentro de um handler de task, `startTransaction()` **retorna a transaction da própria run** ([`ctx.tx`](./concepts.md#transaction-de-run-ctxtx)) em vez de criar uma segunda — cada execução tem exatamente **uma** transaction. Fora de um handler, ela cria uma transaction nova, sem correlação com run nenhuma.
|
|
118
118
|
|
|
119
119
|
## Crons (check-ins)
|
|
120
120
|
|
|
121
|
-
Monitora jobs agendados: um check-in `in_progress` na entrada, `ok`/`error` na saída, com duração medida. O jeito recomendado é `withMonitor`, que faz o par e captura exceções:
|
|
121
|
+
Monitora jobs agendados — o módulo **Monitores** do app: um check-in `in_progress` na entrada, `ok`/`error` na saída, com duração medida. O jeito recomendado é `withMonitor`, que faz o par e captura exceções:
|
|
122
122
|
|
|
123
123
|
```js
|
|
124
124
|
await Hobots.withMonitor(
|
|
@@ -153,7 +153,7 @@ Hobots.init({
|
|
|
153
153
|
|
|
154
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.
|
|
155
155
|
|
|
156
|
-
Essa identidade é a mesma que o
|
|
156
|
+
Essa identidade é a mesma que o poll de tasks envia (`agent.instance_id`) — os dois canais derivam do `instanceId` do `init()`, então o robô aparece como **um agente só** no módulo Agentes automaticamente. Por isso, com `tasks: true` o heartbeat é **opcional** (o poll já marca a presença do agente); ele é essencial para agentes só de telemetria.
|
|
157
157
|
|
|
158
158
|
## Flush e close
|
|
159
159
|
|
|
@@ -161,13 +161,13 @@ Nada é enviado na hora: cada evento, transaction, check-in e heartbeat entra nu
|
|
|
161
161
|
|
|
162
162
|
```js
|
|
163
163
|
await Hobots.flush(2000); // envia o que está em buffer, mantém o SDK vivo
|
|
164
|
-
await Hobots.close(2000); // para o
|
|
164
|
+
await Hobots.close(2000); // para o polling de tasks (se ativo) + heartbeat + flush final do transport
|
|
165
165
|
```
|
|
166
166
|
|
|
167
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
168
|
|
|
169
|
-
Chame `close` antes do processo sair. Os timers
|
|
169
|
+
Chame `close` antes do processo sair. Os timers da telemetria são `unref`'d, então ela **não** segura o processo vivo sozinha (só o `start()` das tasks segura).
|
|
170
170
|
|
|
171
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
172
|
|
|
173
|
-
> **Atenção:** o `timeoutMs` do `close()` é gasto **duas vezes, em sequência** — uma esperando as runs
|
|
173
|
+
> **Atenção:** o `timeoutMs` do `close()` é gasto **duas vezes, em sequência** — uma esperando as runs de tasks, 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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
O SDK é um **cliente HTTP puro**, sem dependências de runtime: ele só fala com o app da Hobots por HTTP. Não abre conexão com banco e **não** roda `SKIP LOCKED` — a reivindicação atômica de runs é server-side (veja o fim desta página).
|
|
4
4
|
|
|
5
|
-
Os arquivos `src/protocol.ts` e `src/
|
|
5
|
+
Os arquivos `src/protocol.ts` e `src/tasks/protocol.ts` são a **fonte de verdade do wire format**: o app valida os payloads com zod travados nesses tipos (`satisfies z.ZodType<...>`), então não podem sofrer drift. Todos os tipos são exportados por `hobots/protocol` (e reexportados pelo import raiz `hobots`).
|
|
6
6
|
|
|
7
7
|
## O Client Secret e os endpoints derivados
|
|
8
8
|
|
|
@@ -14,20 +14,18 @@ hb_<64 hex>
|
|
|
14
14
|
|
|
15
15
|
| Canal | Endpoint | Autenticação |
|
|
16
16
|
| --- | --- | --- |
|
|
17
|
-
|
|
|
18
|
-
|
|
|
17
|
+
| Telemetria (ingestão) | `POST <baseUrl>/v1/ingest` | `Authorization: Bearer <clientSecret>` |
|
|
18
|
+
| Tasks (execução) | `<baseUrl>/v1/tasks/*` | `Authorization: Bearer <clientSecret>` |
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
A resolução da credencial nos endpoints é interna ao `init()` (em `src/config.ts`, com a constante exportada `DEFAULT_BASE_URL = 'https://api2.hobots.app'`); o resultado fica acessível em `getClient()?.config` → `{ key, baseUrl, ingestEndpoint, tasksBaseUrl }`. Em desenvolvimento a env `HOBOTS_API_URL` sobrescreve a URL base — apenas `http(s)`, e `http` para host fora de loopback gera aviso porque o secret trafega sem TLS. A credencial é **secreta** (formato `hb_` + 64 hex — chaves de outro formato são rejeitadas no `init()`) e viaja **só no header** `Authorization: Bearer` — nunca na URL da requisição.
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
## Monitor — ingestão
|
|
22
|
+
## Telemetria — ingestão
|
|
25
23
|
|
|
26
24
|
Endpoint fixo `POST /v1/ingest` recebe **envelopes** JSON autenticados por Bearer. O transport mantém uma fila em memória, agrupa itens em envelopes e faz POST via `fetch`.
|
|
27
25
|
|
|
28
26
|
```jsonc
|
|
29
27
|
{
|
|
30
|
-
"sdk": { "name": "hobots", "version": "
|
|
28
|
+
"sdk": { "name": "hobots", "version": "2.0.0" },
|
|
31
29
|
"sent_at": "2024-01-01T00:00:00.000Z",
|
|
32
30
|
"items": [
|
|
33
31
|
{ "type": "event", "payload": { /* EventPayload */ } },
|
|
@@ -40,24 +38,24 @@ Endpoint fixo `POST /v1/ingest` recebe **envelopes** JSON autenticados por Beare
|
|
|
40
38
|
|
|
41
39
|
Tipos de item: `event`, `transaction`, `check_in`, `heartbeat` (definidos em `hobots/protocol`).
|
|
42
40
|
|
|
43
|
-
Um `transaction` pode carregar `run_id`: é assim que a [transaction de uma run
|
|
41
|
+
Um `transaction` pode carregar `run_id`: é assim que a [transaction de uma run](./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()` fora de uma run não têm esse campo.
|
|
44
42
|
|
|
45
43
|
**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).
|
|
46
44
|
|
|
47
|
-
##
|
|
45
|
+
## Tasks — polling e reporte
|
|
48
46
|
|
|
49
|
-
Dois endpoints sob `<baseUrl>/v1/
|
|
47
|
+
Dois endpoints sob `<baseUrl>/v1/tasks`, ambos com `Authorization: Bearer <clientSecret>` (a credencial única do projeto):
|
|
50
48
|
|
|
51
|
-
### `POST /v1/
|
|
49
|
+
### `POST /v1/tasks/poll`
|
|
52
50
|
|
|
53
|
-
Corpo `
|
|
51
|
+
Corpo `PollRequest`:
|
|
54
52
|
|
|
55
53
|
```jsonc
|
|
56
54
|
{
|
|
57
55
|
"agent": {
|
|
58
56
|
"instance_id": "vm-01", // identidade canônica do agente (obrigatória)
|
|
59
57
|
"name": "VM 01",
|
|
60
|
-
"version": "
|
|
58
|
+
"version": "2.0.0",
|
|
61
59
|
"host": "vm-01",
|
|
62
60
|
"tasks": ["processar-nfe"]
|
|
63
61
|
},
|
|
@@ -66,15 +64,15 @@ Corpo `ManagerPollRequest`:
|
|
|
66
64
|
}
|
|
67
65
|
```
|
|
68
66
|
|
|
69
|
-
Resposta `
|
|
67
|
+
Resposta `PollResponse` = `{ runs: RunDescriptor[] }`, cada `{ id, task, params, form, source, created_at }`.
|
|
70
68
|
|
|
71
|
-
O `agent.instance_id` é a **mesma identidade** do heartbeat
|
|
69
|
+
O `agent.instance_id` é a **mesma identidade** do heartbeat de telemetria (`instance_id`): o servidor correlaciona os dois canais numa tabela `agents` por `(project_id, instance_id)`. Com o `init()` único, os dois derivam do `instanceId` — o robô aparece como um agente só no painel automaticamente. O próprio poll marca a presença do agente (`last_poll_at`), por isso o heartbeat é opcional quando as tasks estão habilitadas.
|
|
72
70
|
|
|
73
71
|
**Backoff:** um request por poll; o loop decide o ritmo. HTTP 401 (key rejeitada) → retry lento a 30s com log throttled; outros erros → ladder `[1s, 4s, 16s]`.
|
|
74
72
|
|
|
75
|
-
### `PATCH /v1/
|
|
73
|
+
### `PATCH /v1/tasks/runs/{id}`
|
|
76
74
|
|
|
77
|
-
Corpo `
|
|
75
|
+
Corpo `RunUpdateRequest`:
|
|
78
76
|
|
|
79
77
|
```jsonc
|
|
80
78
|
{
|
|
@@ -88,17 +86,15 @@ Corpo `ManagerRunUpdateRequest`:
|
|
|
88
86
|
- Ordem garantida `running → logs → terminal` (PATCHes serializados numa cadeia de promise).
|
|
89
87
|
- Retry `[1s, 4s, 16s]`; 4xx exceto 429 é terminal (**409** = run cancelada / transição inválida → marca a run como morta e dispara `ctx.signal`).
|
|
90
88
|
- Todo PATCH atualiza `last_report_at` no servidor (relógio do servidor) — é o **sinal de vida da run**. O heartbeat de cancelamento (PATCH `running` vazio a cada `cancelCheckIntervalMs`, máx 15s) garante reporte contínuo; um sweep server-side marca como `failed` com erro `AgentTerminated` ("execução encerrada inesperadamente") toda run `running` sem reporte há **60s**, sem re-entrega. No exit path (sinal/erro fatal com runs ativas), o SDK envia um único PATCH `failed`/`AgentTerminated` com timeout curto (~2s), sem retry — o sweep é o backstop.
|
|
91
|
-
- O protocolo **não transporta resultado** de execução: o retorno do handler é ignorado pelo SDK e o servidor descarta qualquer campo `result`
|
|
89
|
+
- O protocolo **não transporta resultado** de execução: o retorno do handler é ignorado pelo SDK e o servidor descarta qualquer campo `result` (dados de negócio podem ser sensíveis e não são coletados).
|
|
92
90
|
|
|
93
|
-
Ciclo de vida completo da run no servidor: `pending → claimed → running → succeeded | failed | canceled`. O agente só transiciona para `running`/`succeeded`/`failed
|
|
91
|
+
Ciclo de vida completo da run no servidor (`RunStatus`): `pending → claimed → running → succeeded | failed | canceled`. O agente só transiciona para `running`/`succeeded`/`failed` (`RunUpdateStatus`).
|
|
94
92
|
|
|
95
93
|
## Onde vive o claim atômico
|
|
96
94
|
|
|
97
|
-
O `POST /v1/
|
|
98
|
-
[`apps/api/lib/
|
|
95
|
+
O `POST /v1/tasks/poll` no app chama `claimPendingRuns()` em
|
|
96
|
+
[`apps/api/lib/tasks/claim.ts`](../../../apps/api/lib/tasks/claim.ts): um CTE que seleciona runs `pending` vencidas com `FOR UPDATE SKIP LOCKED` e as promove a `claimed` num único `UPDATE ... RETURNING`. Isso garante que dois agentes concorrentes nunca peguem a mesma run. **Nada disso está no SDK** — ele só faz o HTTP.
|
|
99
97
|
|
|
100
98
|
## Identidade do SDK
|
|
101
99
|
|
|
102
|
-
`SDK_NAME = 'hobots'` e `SDK_VERSION` ficam em `src/utils.ts` e vão em todo envelope (campo `sdk`) e no `agent.version` do poll.
|
|
103
|
-
|
|
104
|
-
> **Nota de manutenção:** `SDK_VERSION` é uma constante fixa e precisa ser mantida em sincronia com a `version` do `package.json` manualmente ao publicar.
|
|
100
|
+
`SDK_NAME = 'hobots'` e `SDK_VERSION` ficam em `src/utils.ts` e vão em todo envelope (campo `sdk`) e no `agent.version` do poll. A `SDK_VERSION` é **injetada pelo build** (o `define` do tsup lê a `version` do `package.json`) — não há sincronização manual ao publicar.
|