hobots 1.0.0 → 1.1.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 +125 -82
- package/dist/index.cjs +462 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +139 -14
- package/dist/index.d.ts +139 -14
- package/dist/index.js +519 -30
- package/dist/index.js.map +1 -1
- package/dist/manager/protocol.cjs.map +1 -1
- package/dist/manager/protocol.d.cts +3 -3
- package/dist/manager/protocol.d.ts +3 -3
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.cts +6 -3
- package/dist/protocol.d.ts +6 -3
- package/docs/README.md +18 -16
- package/docs/api-reference.md +33 -20
- package/docs/getting-started.md +42 -27
- package/docs/manager.md +30 -25
- package/docs/monitor.md +27 -25
- package/docs/protocol.md +26 -12
- package/package.json +10 -15
- package/dist/chunk-FW32OR7V.js +0 -75
- package/dist/chunk-FW32OR7V.js.map +0 -1
- package/dist/manager.cjs +0 -519
- package/dist/manager.cjs.map +0 -1
- package/dist/manager.d.cts +0 -118
- package/dist/manager.d.ts +0 -118
- package/dist/manager.js +0 -464
- package/dist/manager.js.map +0 -1
- package/dist/utils-LReLqjzN.d.cts +0 -7
- package/dist/utils-LReLqjzN.d.ts +0 -7
package/docs/api-reference.md
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
| Import | Conteúdo |
|
|
6
6
|
| --- | --- |
|
|
7
|
-
| `hobots` |
|
|
8
|
-
| `hobots/protocol` | Tipos do wire format
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
| `hobots` | Tudo: `init` único, funções do Monitor, `register`/`start`/`stop` do Manager, `close`, classes (`Client`, `Scope`, `Transaction`, `Span`, `ManagerAgent`, `ManagerApi`), `parseDsn` e tipos. |
|
|
8
|
+
| `hobots/protocol` | Tipos do wire format (Monitor e Manager). |
|
|
9
|
+
|
|
10
|
+
`hobots/manager/protocol` segue existindo como alias transitório dos tipos do wire format do Manager.
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -29,9 +29,19 @@
|
|
|
29
29
|
| `checkIn` | `checkIn(options: CheckInOptions): string \| undefined` | check_in_id |
|
|
30
30
|
| `withMonitor` | `withMonitor<T>(slug: string, fn: () => T \| Promise<T>, options?: WithMonitorOptions): Promise<T>` | retorno de `fn` |
|
|
31
31
|
| `flush` | `flush(timeoutMs?: number): Promise<boolean>` | enviou tudo? |
|
|
32
|
-
| `close` | `close(timeoutMs?: number): Promise<boolean>` |
|
|
32
|
+
| `close` | `close(timeoutMs?: number): Promise<boolean>` | para o Manager (aguarda runs) + heartbeat + flush final do transport |
|
|
33
|
+
|
|
34
|
+
Classes exportadas: `Client`, `Scope`, `Transaction`, `Span`. Função `parseDsn(dsn): ParsedDsn` — parseia a DSN única do projeto:
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
```ts
|
|
37
|
+
interface ParsedDsn {
|
|
38
|
+
key: string; // chave secreta do projeto (vai no Authorization: Bearer, nunca na URL)
|
|
39
|
+
projectSlug: string; // slug do projeto (informativo — a identidade real vem da chave)
|
|
40
|
+
origin: string;
|
|
41
|
+
ingestEndpoint: string; // <origin>/api/ingest
|
|
42
|
+
managerBaseUrl: string; // <origin>/api/manager
|
|
43
|
+
}
|
|
44
|
+
```
|
|
35
45
|
|
|
36
46
|
### Métodos de `Transaction` / `Span`
|
|
37
47
|
|
|
@@ -43,14 +53,17 @@ Classes exportadas: `Client`, `Scope`, `Transaction`, `Span`. Função `parseDsn
|
|
|
43
53
|
|
|
44
54
|
## Manager — funções
|
|
45
55
|
|
|
56
|
+
Habilitado com `manager: true` (ou `ManagerOptions`) no `init()` único. Todas exportadas do import raiz `hobots`:
|
|
57
|
+
|
|
46
58
|
| Função | Assinatura |
|
|
47
59
|
| --- | --- |
|
|
48
|
-
| `init` | `init(options: ManagerInitOptions): void` |
|
|
49
60
|
| `register` | `register<P, R>(task: string, handler: ManagerTaskHandler<P, R>): void` |
|
|
50
|
-
| `start` | `start(): void` |
|
|
51
|
-
| `stop` | `stop(timeoutMs?: number): Promise<boolean>` |
|
|
61
|
+
| `start` | `start(): void` — lança se o `init()` não configurou `manager` ou sem tasks registradas |
|
|
62
|
+
| `stop` | `stop(timeoutMs?: number): Promise<boolean>` — para só o Manager; a telemetria continua |
|
|
52
63
|
|
|
53
|
-
Classes/erros exportados: `ManagerAgent`, `ManagerApi`, `ManagerApiError`.
|
|
64
|
+
Classes/erros exportados: `ManagerAgent`, `ManagerApi`, `ManagerApiError`.
|
|
65
|
+
|
|
66
|
+
No poll, o agente se identifica com `ManagerAgentInfo` (`hobots/protocol`): o campo `instance_id` (o `instanceId` do `init()`) é a identidade canônica do agente — a mesma do heartbeat do Monitor, então os dois canais convergem em um agente no painel.
|
|
54
67
|
|
|
55
68
|
---
|
|
56
69
|
|
|
@@ -60,16 +73,17 @@ Classes/erros exportados: `ManagerAgent`, `ManagerApi`, `ManagerApiError`. Funç
|
|
|
60
73
|
|
|
61
74
|
| Opção | Tipo | Default | Descrição |
|
|
62
75
|
| --- | --- | --- | --- |
|
|
63
|
-
| `dsn` | `string` | — | **obrigatório**;
|
|
76
|
+
| `dsn` | `string` | — | **obrigatório**; a DSN única do projeto: `https://<secretKey>@host/<projectSlug>` |
|
|
64
77
|
| `environment` | `string` | `'production'` | ambiente lógico |
|
|
65
78
|
| `release` | `string` | — | versão do programa, ex. `robo-nfe@1.4.2` |
|
|
66
|
-
| `
|
|
79
|
+
| `instanceId` | `string` | **obrigatório** | identidade única da instância; heartbeat do Monitor e agente do Manager convergem nela |
|
|
67
80
|
| `tags` | `Record<string,string>` | — | tags aplicadas a todos os eventos |
|
|
68
81
|
| `sampleRate` | `number` | `1` | amostragem de erros (0..1) |
|
|
69
82
|
| `tracesSampleRate` | `number` | `1` | amostragem de transactions (0..1) |
|
|
70
83
|
| `maxBreadcrumbs` | `number` | `100` | tamanho do buffer de breadcrumbs |
|
|
71
84
|
| `captureUnhandled` | `boolean` | `true` | handlers globais de erro |
|
|
72
85
|
| `heartbeat` | `boolean \| HeartbeatOptions` | `false` | heartbeat periódico |
|
|
86
|
+
| `manager` | `boolean \| ManagerOptions` | `false` | habilita o agente Manager (`true` usa defaults) |
|
|
73
87
|
| `beforeSend` | `(event) => event \| null` | — | edita/descarta evento (retorne `null` para descartar) |
|
|
74
88
|
| `debug` | `boolean` | `false` | loga atividade do SDK |
|
|
75
89
|
| `flushIntervalMs` | `number` | `2000` | intervalo do flush automático do transport |
|
|
@@ -80,8 +94,7 @@ Classes/erros exportados: `ManagerAgent`, `ManagerApi`, `ManagerApiError`. Funç
|
|
|
80
94
|
| Opção | Tipo | Default | Descrição |
|
|
81
95
|
| --- | --- | --- | --- |
|
|
82
96
|
| `intervalMs` | `number` | `30000` | intervalo entre batimentos |
|
|
83
|
-
| `
|
|
84
|
-
| `name` | `string` | — | nome amigável no dashboard |
|
|
97
|
+
| `name` | `string` | o `instanceId` | nome amigável no dashboard |
|
|
85
98
|
| `metadata` | `Record<string,unknown>` | — | metadados anexados ao batimento |
|
|
86
99
|
|
|
87
100
|
### CaptureHint
|
|
@@ -113,23 +126,23 @@ Classes/erros exportados: `ManagerAgent`, `ManagerApi`, `ManagerApiError`. Funç
|
|
|
113
126
|
| `schedule` | `string` | expressão cron (upsert do monitor) |
|
|
114
127
|
| `timezone` | `string` | fuso do `schedule` |
|
|
115
128
|
|
|
116
|
-
###
|
|
129
|
+
### ManagerOptions
|
|
130
|
+
|
|
131
|
+
Valor do campo `manager` do `InitOptions`. A DSN, o `debug` e a identidade (`instanceId`) vêm do `init()`.
|
|
117
132
|
|
|
118
133
|
| Opção | Tipo | Default | Descrição |
|
|
119
134
|
| --- | --- | --- | --- |
|
|
120
|
-
| `dsn` | `string` | — | **obrigatório**, com a **manager key** (não a chave de ingestão) |
|
|
121
135
|
| `pollIntervalMs` | `number` | `5000` | intervalo entre polls sem trabalho (mínimo `1000`) |
|
|
122
136
|
| `concurrency` | `number` | `1` | runs simultâneas (clamp 1..5) |
|
|
123
|
-
| `agentName` | `string` | hostname | nome exibido no dashboard |
|
|
124
137
|
| `taskTimeoutMs` | `number` | — | timeout client-side por run (não mata o handler; servidor é o backstop) |
|
|
125
138
|
| `logFlushIntervalMs` | `number` | `2000` | intervalo do envio incremental de logs |
|
|
126
|
-
| `debug` | `boolean` | `false` | loga atividade do agente |
|
|
127
139
|
|
|
128
140
|
### ManagerTaskHandler / RunContext
|
|
129
141
|
|
|
130
142
|
```ts
|
|
131
|
-
|
|
132
|
-
|
|
143
|
+
// o retorno do handler é ignorado — nada além de status/logs/erro é enviado ao servidor
|
|
144
|
+
type ManagerTaskHandler<P = Record<string, unknown>> =
|
|
145
|
+
(params: P, ctx: RunContext) => void | Promise<void>;
|
|
133
146
|
|
|
134
147
|
interface RunContext {
|
|
135
148
|
runId: string;
|
package/docs/getting-started.md
CHANGED
|
@@ -10,37 +10,39 @@ Sem dependências de runtime. Requer **Node 18+** (usa o `fetch` global). O Moni
|
|
|
10
10
|
|
|
11
11
|
## O DSN
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Cada projeto tem **uma única DSN** — a mesma credencial autentica o Monitor **e** o Manager:
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
```
|
|
16
|
+
<protocolo>://<secretKey>@<host>[:porta]/<projectSlug>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
A `secretKey` (64 hex) é **secreta**: ela vai embutida na DSN só por conveniência — nas requisições viaja sempre no header `Authorization: Bearer`, nunca na URL. O `projectSlug` no path é informativo; a identidade do projeto vem da chave. Copie a DSN pronta no app, em **Configurações → Agentes** — os snippets de integração já vêm prontos.
|
|
17
20
|
|
|
18
21
|
```
|
|
19
|
-
|
|
20
|
-
Manager: http://<managerKey>@localhost:3002/<projectSlug>
|
|
22
|
+
http://<secretKey>@localhost:3002/<projectSlug>
|
|
21
23
|
```
|
|
22
24
|
|
|
23
|
-
Veja [Protocolo & transporte](./protocol.md) para os endpoints que
|
|
25
|
+
Veja [Protocolo & transporte](./protocol.md) para os endpoints que a DSN deriva.
|
|
24
26
|
|
|
25
|
-
## Monitor
|
|
27
|
+
## Monitor e Manager — qual usar
|
|
26
28
|
|
|
27
|
-
As duas capabilities
|
|
29
|
+
As duas capabilities vivem no mesmo `init()` e você usa **uma, outra ou as duas**:
|
|
28
30
|
|
|
29
|
-
- Quer só saber se o robô quebrou, quanto demora e se está vivo? →
|
|
30
|
-
- Quer disparar o robô sob demanda a partir de um formulário no app? →
|
|
31
|
-
- Ambos?
|
|
32
|
-
[`
|
|
31
|
+
- Quer só saber se o robô quebrou, quanto demora e se está vivo? → `init({ dsn, heartbeat: true })`.
|
|
32
|
+
- Quer disparar o robô sob demanda a partir de um formulário no app? → `init({ dsn, manager: true })` + `register()` + `start()`.
|
|
33
|
+
- Ambos? Passe `heartbeat` e `manager` no **mesmo `init()`**. É o cenário do
|
|
34
|
+
[`full.ts`](../../hobots-sdk-demos/src/full.ts) nos demos do SDK.
|
|
33
35
|
|
|
34
|
-
##
|
|
36
|
+
## O `init` único
|
|
35
37
|
|
|
36
38
|
```js
|
|
37
|
-
import * as
|
|
39
|
+
import * as Hobots from 'hobots';
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
dsn: 'http://<
|
|
41
|
+
Hobots.init({
|
|
42
|
+
dsn: 'http://<secretKey>@localhost:3002/<projectSlug>',
|
|
41
43
|
environment: 'production',
|
|
42
44
|
release: 'robo-nfe@1.4.2',
|
|
43
|
-
|
|
45
|
+
instanceId: 'vm-cliente-01', // identidade única: heartbeat + agente Manager
|
|
44
46
|
heartbeat: true, // "estou vivo" a cada 30s
|
|
45
47
|
});
|
|
46
48
|
|
|
@@ -49,40 +51,53 @@ Monitor.init({
|
|
|
49
51
|
try {
|
|
50
52
|
await processarNota(nota);
|
|
51
53
|
} catch (error) {
|
|
52
|
-
|
|
54
|
+
Hobots.captureException(error, { tags: { cliente: 'acme' } });
|
|
53
55
|
throw error;
|
|
54
56
|
}
|
|
55
57
|
```
|
|
56
58
|
|
|
57
|
-
Chame `
|
|
59
|
+
Chame `Hobots.init` **uma vez**, o mais cedo possível no processo. Detalhes em [Monitor](./monitor.md).
|
|
58
60
|
|
|
59
61
|
## Primeiro agente (Manager)
|
|
60
62
|
|
|
63
|
+
Habilite a execução sob demanda com o campo `manager` no mesmo `init()`:
|
|
64
|
+
|
|
61
65
|
```js
|
|
62
|
-
import * as
|
|
66
|
+
import * as Hobots from 'hobots';
|
|
63
67
|
|
|
64
|
-
|
|
65
|
-
|
|
68
|
+
Hobots.init({
|
|
69
|
+
dsn: 'http://<secretKey>@localhost:3002/<projectSlug>',
|
|
70
|
+
instanceId: 'vm-cliente-01',
|
|
71
|
+
manager: true, // ou { pollIntervalMs, concurrency, ... }
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
Hobots.register('processar-nfe', async (params, ctx) => {
|
|
66
75
|
ctx.log('info', `processando lote ${params.lote}…`);
|
|
67
76
|
return { processadas: 12 };
|
|
68
77
|
});
|
|
69
|
-
|
|
78
|
+
Hobots.start(); // mantém o processo vivo até stop()/close()
|
|
70
79
|
```
|
|
71
80
|
|
|
81
|
+
> O `instanceId` é a identidade dos **dois** canais (heartbeat do Monitor e agente do Manager) — o robô aparece como **um agente só** no painel, sem configuração extra.
|
|
82
|
+
|
|
72
83
|
Detalhes (cancelamento, concorrência, idempotência) em [Manager](./manager.md).
|
|
73
84
|
|
|
74
85
|
## Encerramento
|
|
75
86
|
|
|
76
|
-
|
|
87
|
+
`close()` encerra tudo na ordem certa: para o Manager (aguardando a run atual), o heartbeat e faz o flush final do transport.
|
|
88
|
+
|
|
89
|
+
Sem Manager iniciado, o SDK não segura o processo (timers `unref`'d), mas faça o flush final para não perder eventos em buffer:
|
|
77
90
|
|
|
78
91
|
```js
|
|
79
|
-
await
|
|
92
|
+
await Hobots.close(2000);
|
|
80
93
|
```
|
|
81
94
|
|
|
82
|
-
|
|
95
|
+
Com o Manager, `start()` segura o processo por design. Encerre de forma graciosa aguardando a run atual:
|
|
83
96
|
|
|
84
97
|
```js
|
|
85
98
|
process.on('SIGINT', () => {
|
|
86
|
-
void
|
|
99
|
+
void Hobots.close(15_000).then(() => process.exit(0));
|
|
87
100
|
});
|
|
88
101
|
```
|
|
102
|
+
|
|
103
|
+
Para parar de aceitar execuções **sem** derrubar a telemetria, use `Hobots.stop(timeoutMs?)`.
|
package/docs/manager.md
CHANGED
|
@@ -1,43 +1,46 @@
|
|
|
1
1
|
# Manager
|
|
2
2
|
|
|
3
|
-
Capability de **execução remota sob demanda
|
|
4
|
-
|
|
5
|
-
```js
|
|
6
|
-
import * as Manager from 'hobots/manager';
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
Independente do Monitor: credencial própria (**manager key**), singletons próprios e nenhum handler global instalado. Pode conviver com `Monitor.init()` no mesmo processo.
|
|
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 a mesma DSN, o mesmo `debug` e o `instanceId` como identidade — nenhum handler global extra é instalado.
|
|
10
4
|
|
|
11
5
|
## Ciclo de vida
|
|
12
6
|
|
|
13
7
|
```js
|
|
14
|
-
|
|
8
|
+
import * as Hobots from 'hobots';
|
|
15
9
|
|
|
16
|
-
|
|
10
|
+
Hobots.init({
|
|
11
|
+
dsn: 'http://<secretKey>@localhost:3002/<projectSlug>',
|
|
12
|
+
instanceId: 'vm-cliente-01',
|
|
13
|
+
manager: true, // ou { pollIntervalMs, concurrency, taskTimeoutMs, logFlushIntervalMs }
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
Hobots.register('processar-nfe', async (params, ctx) => {
|
|
17
17
|
ctx.log('info', `processando lote ${params.lote}…`);
|
|
18
18
|
if (ctx.signal.aborted) return; // cancelamento cooperativo
|
|
19
|
-
|
|
19
|
+
ctx.log('info', '12 notas processadas');
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Hobots.start(); // começa o polling e MANTÉM O PROCESSO VIVO
|
|
23
23
|
|
|
24
24
|
process.on('SIGINT', () => {
|
|
25
|
-
void
|
|
25
|
+
void Hobots.close(15_000).then(() => process.exit(0));
|
|
26
26
|
});
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
- `init(
|
|
30
|
-
- `register(task, handler)` — `task` é o `task_slug` configurado no formulário do app. Registre **antes** de `start()`.
|
|
31
|
-
- `start()` — começa o polling. Lança se `init()` não
|
|
32
|
-
- `stop(timeoutMs?)` — para o polling e aguarda as runs ativas (default 30s). Retorna `false` se estourou o timeout.
|
|
29
|
+
- `init({ manager })` — cria o agente. Chamar `init()` de novo para o agente anterior e o substitui (os handlers registrados são preservados).
|
|
30
|
+
- `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
|
+
- `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.
|
|
33
|
+
- `close(timeoutMs?)` — encerra **tudo**: agente (aguardando runs em voo) → heartbeat → flush final.
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
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.
|
|
36
|
+
|
|
37
|
+
> **Atenção:** ao contrário da telemetria, `start()` **segura o processo vivo** aguardando trabalho até `stop()`/`close()` — o agente é a razão do processo existir.
|
|
35
38
|
|
|
36
39
|
## O handler e o `RunContext`
|
|
37
40
|
|
|
38
41
|
```ts
|
|
39
|
-
type ManagerTaskHandler<P = Record<string, unknown
|
|
40
|
-
(params: P, ctx: RunContext) =>
|
|
42
|
+
type ManagerTaskHandler<P = Record<string, unknown>> =
|
|
43
|
+
(params: P, ctx: RunContext) => void | Promise<void>;
|
|
41
44
|
|
|
42
45
|
interface RunContext {
|
|
43
46
|
runId: string;
|
|
@@ -50,21 +53,23 @@ interface RunContext {
|
|
|
50
53
|
- **`params`** — os dados do formulário configurado no app (o JSON validado).
|
|
51
54
|
- **`ctx.log(level, message)`** — logs enviados **incrementalmente** ao app (PATCHes com throttle), visíveis na run em tempo quase real.
|
|
52
55
|
- **`ctx.signal`** — `AbortSignal` que dispara quando a run é **cancelada no app**, em **timeout** ou no **shutdown**. Cancelamento é cooperativo (veja abaixo).
|
|
53
|
-
- **Retorno** —
|
|
56
|
+
- **Retorno** — **ignorado**. O SDK não captura nem envia o valor retornado pelo handler ao servidor — resultados de execução podem conter dados sensíveis e não são coletados.
|
|
54
57
|
- **Erro lançado** — a run é reportada como `failed` com tipo/mensagem/stack (mensagem cap 10k, stack 20k).
|
|
55
58
|
|
|
56
|
-
Você pode tipar params
|
|
59
|
+
Você pode tipar os params: `Hobots.register<{ lote: number }>(...)`.
|
|
60
|
+
|
|
61
|
+
> **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.
|
|
57
62
|
|
|
58
63
|
## Cancelamento cooperativo
|
|
59
64
|
|
|
60
65
|
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`. **Verifique `ctx.signal.aborted`** em loops longos e pontos de parada:
|
|
61
66
|
|
|
62
67
|
```js
|
|
63
|
-
|
|
68
|
+
Hobots.register('processar-nfe', async (params, ctx) => {
|
|
64
69
|
for (const nota of params.notas) {
|
|
65
70
|
if (ctx.signal.aborted) {
|
|
66
71
|
ctx.log('warning', 'cancelamento detectado — parando');
|
|
67
|
-
return
|
|
72
|
+
return;
|
|
68
73
|
}
|
|
69
74
|
await processar(nota);
|
|
70
75
|
}
|
|
@@ -73,7 +78,7 @@ Manager.register('processar-nfe', async (params, ctx) => {
|
|
|
73
78
|
|
|
74
79
|
## Idempotência (at-least-once)
|
|
75
80
|
|
|
76
|
-
A entrega é **at-least-once**: se o agente morrer logo após capturar uma run (antes de reportar
|
|
81
|
+
A entrega é **at-least-once**: se o agente morrer logo após capturar uma run (antes de reportar a conclusão), ela é re-entregue quando o agente voltar. Escreva handlers **idempotentes** — 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).
|
|
77
82
|
|
|
78
83
|
## Concorrência
|
|
79
84
|
|
|
@@ -83,7 +88,7 @@ A entrega é **at-least-once**: se o agente morrer logo após capturar uma run (
|
|
|
83
88
|
|
|
84
89
|
`taskTimeoutMs` é um timeout **client-side**. No estouro o SDK **não mata** o handler — ele dispara `ctx.signal`, libera o slot de concorrência e reporta a run como falha (`TaskTimeout`). O handler pode continuar rodando em background; respeite o `signal` para parar de verdade. Sem `taskTimeoutMs`, o backstop é o servidor.
|
|
85
90
|
|
|
86
|
-
Todas as opções em [Referência de API →
|
|
91
|
+
Todas as opções em [Referência de API → ManagerOptions](./api-reference.md#manageroptions).
|
|
87
92
|
|
|
88
93
|
## Como o claim funciona (resumo)
|
|
89
94
|
|
package/docs/monitor.md
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
# Monitor
|
|
2
2
|
|
|
3
|
-
Capability de telemetria: erros, performance, crons e uptime.
|
|
3
|
+
Capability de telemetria: erros, performance, crons e uptime. Sempre ativa no `init()` único:
|
|
4
4
|
|
|
5
5
|
```js
|
|
6
|
-
import * as
|
|
6
|
+
import * as Hobots from 'hobots';
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Todas as funções abaixo são no-op seguros se `
|
|
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).
|
|
10
10
|
|
|
11
11
|
## init
|
|
12
12
|
|
|
13
13
|
```js
|
|
14
|
-
|
|
15
|
-
dsn: 'http://<
|
|
14
|
+
Hobots.init({
|
|
15
|
+
dsn: 'http://<secretKey>@localhost:3002/<projectSlug>',
|
|
16
16
|
environment: 'production',
|
|
17
17
|
release: 'robo-nfe@1.4.2',
|
|
18
|
-
|
|
18
|
+
instanceId: 'vm-cliente-01',
|
|
19
19
|
tags: { squad: 'automacoes' },
|
|
20
20
|
heartbeat: true,
|
|
21
21
|
});
|
|
@@ -27,14 +27,14 @@ Cria o cliente, instala handlers globais de erro (a menos que `captureUnhandled:
|
|
|
27
27
|
|
|
28
28
|
```js
|
|
29
29
|
// exceções
|
|
30
|
-
|
|
30
|
+
Hobots.captureException(error, {
|
|
31
31
|
tags: { cliente: 'acme' },
|
|
32
32
|
level: 'error',
|
|
33
33
|
fingerprint: ['sefaz-timeout'], // agrupa manualmente; sem isso o servidor agrupa pelo stack
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
// mensagens sem exceção
|
|
37
|
-
|
|
37
|
+
Hobots.captureMessage('Lote processado com sucesso', 'info');
|
|
38
38
|
```
|
|
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`).
|
|
@@ -46,7 +46,7 @@ Erros **não tratados** (`uncaughtException` / `unhandledRejection`) são captur
|
|
|
46
46
|
Trilha do que aconteceu antes do erro; enviada junto do próximo evento.
|
|
47
47
|
|
|
48
48
|
```js
|
|
49
|
-
|
|
49
|
+
Hobots.addBreadcrumb({ category: 'nfe', message: 'Baixou XML da nota 123', level: 'info' });
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
O buffer é limitado por `maxBreadcrumbs` (default 100, mais antigos descartados).
|
|
@@ -56,26 +56,26 @@ O buffer é limitado por `maxBreadcrumbs` (default 100, mais antigos descartados
|
|
|
56
56
|
Dados anexados a **todos** os eventos seguintes:
|
|
57
57
|
|
|
58
58
|
```js
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
Hobots.setTag('regiao', 'nordeste');
|
|
60
|
+
Hobots.setTags({ squad: 'automacoes', cliente: 'acme' });
|
|
61
|
+
Hobots.setUser({ id: 'op-42', email: 'op@acme.com' });
|
|
62
|
+
Hobots.setContext('vm', { cpu: 4, ram: '8GB' });
|
|
63
|
+
Hobots.setExtra('ultimoLote', 128);
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
Para um escopo **isolado** (sem vazar para o global), use `withScope`:
|
|
67
67
|
|
|
68
68
|
```js
|
|
69
|
-
|
|
69
|
+
Hobots.withScope((scope) => {
|
|
70
70
|
scope.setTag('lote', '128').setFingerprint(['reprocessamento']);
|
|
71
|
-
|
|
71
|
+
Hobots.captureMessage('Reprocessando lote 128');
|
|
72
72
|
});
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
## Performance: transactions e spans
|
|
76
76
|
|
|
77
77
|
```js
|
|
78
|
-
const tx =
|
|
78
|
+
const tx = Hobots.startTransaction('processar-lote', 'rpa.job');
|
|
79
79
|
|
|
80
80
|
const span = tx.startChild('http.client', 'GET /api/notas');
|
|
81
81
|
await api.buscarNotas();
|
|
@@ -98,7 +98,7 @@ tx.finish(); // só aqui a transaction é enviada (respeitando tracesSampleRate)
|
|
|
98
98
|
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:
|
|
99
99
|
|
|
100
100
|
```js
|
|
101
|
-
await
|
|
101
|
+
await Hobots.withMonitor(
|
|
102
102
|
'sync-notas',
|
|
103
103
|
() => executarSync(),
|
|
104
104
|
{ schedule: '0 */2 * * *', timezone: 'America/Fortaleza' }
|
|
@@ -108,9 +108,9 @@ await Monitor.withMonitor(
|
|
|
108
108
|
O `schedule` (expressão cron) faz o servidor **criar/atualizar o monitor automaticamente** (upsert) e habilita a detecção de **execução perdida**. Para controle manual, use `checkIn` diretamente (veja [Referência](./api-reference.md#checkinoptions)):
|
|
109
109
|
|
|
110
110
|
```js
|
|
111
|
-
const id =
|
|
111
|
+
const id = Hobots.checkIn({ monitorSlug: 'sync-notas', status: 'in_progress' });
|
|
112
112
|
// … trabalho …
|
|
113
|
-
|
|
113
|
+
Hobots.checkIn({ monitorSlug: 'sync-notas', status: 'ok', checkInId: id, durationMs: 1234 });
|
|
114
114
|
```
|
|
115
115
|
|
|
116
116
|
## Heartbeat (uptime)
|
|
@@ -118,19 +118,21 @@ Monitor.checkIn({ monitorSlug: 'sync-notas', status: 'ok', checkInId: id, durati
|
|
|
118
118
|
Bate "estou vivo" periodicamente; o servidor acusa o robô **offline** quando os batimentos param.
|
|
119
119
|
|
|
120
120
|
```js
|
|
121
|
-
|
|
121
|
+
Hobots.init({
|
|
122
122
|
dsn,
|
|
123
123
|
heartbeat: { intervalMs: 30_000, name: 'Robô NFe — VM 01' },
|
|
124
124
|
});
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
-
`heartbeat: true` usa os defaults.
|
|
127
|
+
`heartbeat: true` usa os defaults. A identidade é o `instanceId` do `init()`. Campos em [HeartbeatOptions](./api-reference.md#heartbeatoptions).
|
|
128
|
+
|
|
129
|
+
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.
|
|
128
130
|
|
|
129
131
|
## Flush e close
|
|
130
132
|
|
|
131
133
|
```js
|
|
132
|
-
await
|
|
133
|
-
await
|
|
134
|
+
await Hobots.flush(2000); // envia o que está em buffer, mantém o SDK vivo
|
|
135
|
+
await Hobots.close(2000); // para o Manager (se ativo) + heartbeat + flush final do transport
|
|
134
136
|
```
|
|
135
137
|
|
|
136
|
-
Chame `close` antes do processo sair. Os timers do Monitor são `unref`'d, então
|
|
138
|
+
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).
|
package/docs/protocol.md
CHANGED
|
@@ -4,19 +4,26 @@ O SDK é um **cliente HTTP puro**, sem dependências de runtime: ele só fala co
|
|
|
4
4
|
|
|
5
5
|
Os arquivos `src/protocol.ts` e `src/manager/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.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## A DSN e os endpoints derivados
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
| --- | --- | --- | --- |
|
|
11
|
-
| Monitor | `<proto>://<publicKey>@host/<projectId>` | `<origin>/api/ingest/<publicKey>` | public key **na URL** (sem header) |
|
|
12
|
-
| Manager | `<proto>://<managerKey>@host/<projectSlug>` | `<origin>/api/manager` | `Authorization: Bearer <managerKey>` (**nunca** na URL) |
|
|
9
|
+
Cada projeto tem **uma única DSN secreta**, a mesma para as duas capabilities:
|
|
13
10
|
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
```
|
|
12
|
+
<proto>://<secretKey>@host[:porta]/<projectSlug>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
| Canal | Endpoint | Autenticação |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| Monitor (ingestão) | `POST <origin>/api/ingest` | `Authorization: Bearer <secretKey>` |
|
|
18
|
+
| Manager (execução) | `<origin>/api/manager/*` | `Authorization: Bearer <secretKey>` |
|
|
19
|
+
|
|
20
|
+
`parseDsn(dsn) → { key, projectSlug, origin, ingestEndpoint, managerBaseUrl }` (em `src/dsn.ts`). A chave é **secreta** (64 hex) e viaja **só no header** `Authorization: Bearer` — nunca na URL da requisição. O `projectSlug` do path é informativo: a identidade real do projeto vem da chave.
|
|
21
|
+
|
|
22
|
+
> A DSN antiga do Monitor (public key na URL, `POST /api/ingest/<key>`) foi descontinuada — o endpoint responde **410** e o transport descarta sem retry. Copie a DSN nova em **Configurações → Agentes** no app.
|
|
16
23
|
|
|
17
24
|
## Monitor — ingestão
|
|
18
25
|
|
|
19
|
-
Endpoint
|
|
26
|
+
Endpoint fixo `POST /api/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`.
|
|
20
27
|
|
|
21
28
|
```jsonc
|
|
22
29
|
{
|
|
@@ -37,7 +44,7 @@ Tipos de item: `event`, `transaction`, `check_in`, `heartbeat` (definidos em `ho
|
|
|
37
44
|
|
|
38
45
|
## Manager — polling e reporte
|
|
39
46
|
|
|
40
|
-
Dois endpoints sob `<origin>/api/manager`, ambos com `Authorization: Bearer <
|
|
47
|
+
Dois endpoints sob `<origin>/api/manager`, ambos com `Authorization: Bearer <secretKey>` (a chave única do projeto):
|
|
41
48
|
|
|
42
49
|
### `POST /api/manager/poll`
|
|
43
50
|
|
|
@@ -45,7 +52,13 @@ Corpo `ManagerPollRequest`:
|
|
|
45
52
|
|
|
46
53
|
```jsonc
|
|
47
54
|
{
|
|
48
|
-
"agent": {
|
|
55
|
+
"agent": {
|
|
56
|
+
"instance_id": "vm-01", // identidade canônica do agente (obrigatória)
|
|
57
|
+
"name": "VM 01",
|
|
58
|
+
"version": "1.0.0",
|
|
59
|
+
"host": "vm-01",
|
|
60
|
+
"tasks": ["processar-nfe"]
|
|
61
|
+
},
|
|
49
62
|
"max": 1, // quantas reivindicar (1..5)
|
|
50
63
|
"tasks": ["processar-nfe"] // só runs destas tasks registradas
|
|
51
64
|
}
|
|
@@ -53,6 +66,8 @@ Corpo `ManagerPollRequest`:
|
|
|
53
66
|
|
|
54
67
|
Resposta `ManagerPollResponse` = `{ runs: ManagerRunDescriptor[] }`, cada `{ id, task, params, form, source, created_at }`.
|
|
55
68
|
|
|
69
|
+
O `agent.instance_id` é a **mesma identidade** do heartbeat do Monitor (`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.
|
|
70
|
+
|
|
56
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]`.
|
|
57
72
|
|
|
58
73
|
### `PATCH /api/manager/runs/{id}`
|
|
@@ -63,7 +78,6 @@ Corpo `ManagerRunUpdateRequest`:
|
|
|
63
78
|
{
|
|
64
79
|
"status": "running", // 'running' | 'succeeded' | 'failed'
|
|
65
80
|
"logs": [{ "ts": "…", "level": "info", "message": "…" }], // delta; o servidor concatena
|
|
66
|
-
"result": { "processadas": 12 },// só com 'succeeded'
|
|
67
81
|
"error": { "type": "Error", "message": "…", "stack": "…" } // só com 'failed'
|
|
68
82
|
}
|
|
69
83
|
```
|
|
@@ -71,7 +85,7 @@ Corpo `ManagerRunUpdateRequest`:
|
|
|
71
85
|
- `status: 'running'` pode repetir para enviar **logs incrementais** (throttle por `logFlushIntervalMs`, flush ao acumular 20, chunk de 100, teto de 500 logs/run, mensagem cap 10k).
|
|
72
86
|
- Ordem garantida `running → logs → terminal` (PATCHes serializados numa cadeia de promise).
|
|
73
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`).
|
|
74
|
-
- `result`
|
|
88
|
+
- O protocolo **não transporta resultado** de execução: o retorno do handler é ignorado pelo SDK e o servidor descarta qualquer campo `result` de SDKs antigos (dados de negócio podem ser sensíveis e não são coletados).
|
|
75
89
|
|
|
76
90
|
Ciclo de vida completo da run no servidor: `pending → claimed → running → succeeded | failed | canceled`. O agente só transiciona para `running`/`succeeded`/`failed`.
|
|
77
91
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hobots",
|
|
3
|
-
"version": "1.
|
|
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.",
|
|
3
|
+
"version": "1.1.0",
|
|
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,
|
|
7
7
|
"license": "MIT",
|
|
@@ -26,28 +26,23 @@
|
|
|
26
26
|
"import": "./dist/protocol.js",
|
|
27
27
|
"require": "./dist/protocol.cjs"
|
|
28
28
|
},
|
|
29
|
-
"./manager": {
|
|
30
|
-
"types": "./dist/manager.d.ts",
|
|
31
|
-
"import": "./dist/manager.js",
|
|
32
|
-
"require": "./dist/manager.cjs"
|
|
33
|
-
},
|
|
34
29
|
"./manager/protocol": {
|
|
35
30
|
"types": "./dist/manager/protocol.d.ts",
|
|
36
31
|
"import": "./dist/manager/protocol.js",
|
|
37
32
|
"require": "./dist/manager/protocol.cjs"
|
|
38
33
|
}
|
|
39
34
|
},
|
|
40
|
-
"devDependencies": {
|
|
41
|
-
"@types/node": "^20",
|
|
42
|
-
"tsup": "^8.4.0",
|
|
43
|
-
"typescript": "5.7.3",
|
|
44
|
-
"@workspace/eslint-config": "0.0.0",
|
|
45
|
-
"@workspace/typescript-config": "0.0.0"
|
|
46
|
-
},
|
|
47
35
|
"scripts": {
|
|
48
36
|
"build": "tsup",
|
|
49
37
|
"dev": "tsup --watch",
|
|
50
38
|
"lint": "eslint . --max-warnings 0",
|
|
51
39
|
"typecheck": "tsc --noEmit"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^20",
|
|
43
|
+
"@workspace/eslint-config": "workspace:*",
|
|
44
|
+
"@workspace/typescript-config": "workspace:*",
|
|
45
|
+
"tsup": "^8.4.0",
|
|
46
|
+
"typescript": "5.7.3"
|
|
52
47
|
}
|
|
53
|
-
}
|
|
48
|
+
}
|