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.
@@ -4,10 +4,10 @@
4
4
 
5
5
  | Import | Conteúdo |
6
6
  | --- | --- |
7
- | `hobots` | Monitor: funções, `Client`, `Scope`, `Transaction`, `Span`, `parseDsn`, tipos. |
8
- | `hobots/protocol` | Tipos do wire format do Monitor. |
9
- | `hobots/manager` | Manager: `init`/`register`/`start`/`stop`, `ManagerAgent`, `ManagerApi`, tipos. |
10
- | `hobots/manager/protocol` | Tipos do wire format do Manager. |
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>` | flush + para heartbeat/transport |
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
- Classes exportadas: `Client`, `Scope`, `Transaction`, `Span`. Função `parseDsn(dsn): ParsedDsn`.
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`. Função `parseManagerDsn(dsn): ParsedManagerDsn`.
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**; `<origin>/<publicKey>/<projectId>` |
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
- | `serverName` | `string` | hostname | identifica a máquina; também vira `instanceId` do heartbeat |
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
- | `instanceId` | `string` | `serverName`/`HOSTNAME`/id aleatório | identificador estável da instância |
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
- ### ManagerInitOptions
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
- type ManagerTaskHandler<P = Record<string, unknown>, R = unknown> =
132
- (params: P, ctx: RunContext) => R | Promise<R>;
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;
@@ -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
- Toda credencial do SDK é um **DSN** no formato `<protocolo>://<key>@<host>[:porta]/<projeto>`. A `key` vai embutida na URL e o `host` aponta para o app da Hobots.
13
+ Cada projeto tem **uma única DSN** — a mesma credencial autentica o Monitor **e** o Manager:
14
14
 
15
- - **Monitor** usa a **public key** (chave de ingestão) e o `projectId`. Você a copia da página do projeto em `/monitor` — o snippet de integração já vem pronto.
16
- - **Manager** usa a **manager key** (chave secreta do agente, criada em `/manager/agents`) e o `projectSlug`. **Não** é a chave de ingestão.
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
- Monitor: http://<publicKey>@localhost:3002/<projectId>
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 cada DSN deriva.
25
+ Veja [Protocolo & transporte](./protocol.md) para os endpoints que a DSN deriva.
24
26
 
25
- ## Monitor vs Manager — qual usar
27
+ ## Monitor e Manager — qual usar
26
28
 
27
- As duas capabilities são independentes e você usa **uma, outra ou as duas**:
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? → **Monitor**.
30
- - Quer disparar o robô sob demanda a partir de um formulário no app? → **Manager**.
31
- - Ambos? Inicialize os dois no mesmo processo (credenciais separadas). É o cenário do
32
- [`demo.ts`](../../../apps/webapp/scripts/demo.ts).
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
- ## Primeiro `init` (Monitor)
36
+ ## O `init` único
35
37
 
36
38
  ```js
37
- import * as Monitor from 'hobots';
39
+ import * as Hobots from 'hobots';
38
40
 
39
- Monitor.init({
40
- dsn: 'http://<publicKey>@localhost:3002/<projectId>',
41
+ Hobots.init({
42
+ dsn: 'http://<secretKey>@localhost:3002/<projectSlug>',
41
43
  environment: 'production',
42
44
  release: 'robo-nfe@1.4.2',
43
- serverName: 'vm-cliente-01',
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
- Monitor.captureException(error, { tags: { cliente: 'acme' } });
54
+ Hobots.captureException(error, { tags: { cliente: 'acme' } });
53
55
  throw error;
54
56
  }
55
57
  ```
56
58
 
57
- Chame `Monitor.init` **uma vez**, o mais cedo possível no processo. Detalhes em [Monitor](./monitor.md).
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 Manager from 'hobots/manager';
66
+ import * as Hobots from 'hobots';
63
67
 
64
- Manager.init({ dsn: 'http://<managerKey>@localhost:3002/<projectSlug>', agentName: 'VM Cliente 01' });
65
- Manager.register('processar-nfe', async (params, ctx) => {
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
- Manager.start(); // mantém o processo vivo até stop()
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
- O **Monitor** não segura o processo (timers `unref`'d), mas faça o flush final para não perder eventos em buffer:
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 Monitor.close(2000);
92
+ await Hobots.close(2000);
80
93
  ```
81
94
 
82
- O **Manager** segura o processo por design. Encerre de forma graciosa aguardando a run atual:
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 Manager.stop(15_000).then(() => process.exit(0));
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**. O agente faz polling no app, executa os handlers registrados quando aparece trabalho e reporta status, logs incrementais e resultado. Entry próprio:
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
- Manager.init({ dsn: 'http://<managerKey>@localhost:3002/<projectSlug>', agentName: 'VM Cliente 01' });
8
+ import * as Hobots from 'hobots';
15
9
 
16
- Manager.register('processar-nfe', async (params, ctx) => {
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
- return { processadas: 12 }; // vira o resultado da execução no app
19
+ ctx.log('info', '12 notas processadas');
20
20
  });
21
21
 
22
- Manager.start(); // começa o polling e MANTÉM O PROCESSO VIVO
22
+ Hobots.start(); // começa o polling e MANTÉM O PROCESSO VIVO
23
23
 
24
24
  process.on('SIGINT', () => {
25
- void Manager.stop(15_000).then(() => process.exit(0));
25
+ void Hobots.close(15_000).then(() => process.exit(0));
26
26
  });
27
27
  ```
28
28
 
29
- - `init(options)` — cria o agente (singleton). Chamar de novo para o anterior e substitui.
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 foi chamado ou se nenhuma task foi registrada.
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
- > **Atenção:** ao contrário do Monitor, `Manager.start()` **segura o processo vivo** aguardando trabalho até `Manager.stop()` — o agente é a razão do processo existir.
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>, R = unknown> =
40
- (params: P, ctx: RunContext) => R | Promise<R>;
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** — vira o resultado da execução exibido no app (serializado em JSON, cap de ~100KB; resultados maiores/circulares são descartados com um marcador `__truncated`).
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 e retorno: `Manager.register<{ lote: number }, { processadas: number }>(...)`.
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
- Manager.register('processar-nfe', async (params, ctx) => {
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 { canceladas: true };
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 o resultado), 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).
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 → ManagerInitOptions](./api-reference.md#managerinitoptions).
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. Import raiz:
3
+ Capability de telemetria: erros, performance, crons e uptime. Sempre ativa no `init()` único:
4
4
 
5
5
  ```js
6
- import * as Monitor from 'hobots';
6
+ import * as Hobots from 'hobots';
7
7
  ```
8
8
 
9
- Todas as funções abaixo são no-op seguros se `Monitor.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).
10
10
 
11
11
  ## init
12
12
 
13
13
  ```js
14
- Monitor.init({
15
- dsn: 'http://<publicKey>@localhost:3002/<projectId>',
14
+ Hobots.init({
15
+ dsn: 'http://<secretKey>@localhost:3002/<projectSlug>',
16
16
  environment: 'production',
17
17
  release: 'robo-nfe@1.4.2',
18
- serverName: 'vm-cliente-01',
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
- Monitor.captureException(error, {
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
- Monitor.captureMessage('Lote processado com sucesso', 'info');
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
- Monitor.addBreadcrumb({ category: 'nfe', message: 'Baixou XML da nota 123', level: 'info' });
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
- Monitor.setTag('regiao', 'nordeste');
60
- Monitor.setTags({ squad: 'automacoes', cliente: 'acme' });
61
- Monitor.setUser({ id: 'op-42', email: 'op@acme.com' });
62
- Monitor.setContext('vm', { cpu: 4, ram: '8GB' });
63
- Monitor.setExtra('ultimoLote', 128);
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
- Monitor.withScope((scope) => {
69
+ Hobots.withScope((scope) => {
70
70
  scope.setTag('lote', '128').setFingerprint(['reprocessamento']);
71
- Monitor.captureMessage('Reprocessando lote 128');
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 = Monitor.startTransaction('processar-lote', 'rpa.job');
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 Monitor.withMonitor(
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 = Monitor.checkIn({ monitorSlug: 'sync-notas', status: 'in_progress' });
111
+ const id = Hobots.checkIn({ monitorSlug: 'sync-notas', status: 'in_progress' });
112
112
  // … trabalho …
113
- Monitor.checkIn({ monitorSlug: 'sync-notas', status: 'ok', checkInId: id, durationMs: 1234 });
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
- Monitor.init({
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. O `instanceId` default é o `serverName` / `HOSTNAME` / um id aleatório por processo. Campos em [HeartbeatOptions](./api-reference.md#heartbeatoptions).
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 Monitor.flush(2000); // envia o que está em buffer, mantém o SDK vivo
133
- await Monitor.close(2000); // flush final + para o heartbeat + fecha o transport
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 ele **não** segura o processo vivo sozinho.
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
- ## DSNs e endpoints derivados
7
+ ## A DSN e os endpoints derivados
8
8
 
9
- | Capability | DSN | Endpoint | Autenticação |
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
- - Monitor: `parseDsn(dsn) → { publicKey, projectId, endpoint }`.
15
- - Manager: `parseManagerDsn(dsn) → { key, projectSlug, origin, baseUrl }`.
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 único `POST /api/ingest/<publicKey>` recebe **envelopes** JSON. O transport mantém uma fila em memória, agrupa itens em envelopes e faz POST via `fetch`.
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 <managerKey>`:
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": { "name": "VM 01", "version": "1.0.0", "host": "vm-01", "tasks": ["processar-nfe"] },
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` é serializado com cap de ~100KB (circular/grande → descartado com marcador `__truncated`).
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.0.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.",
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
+ }