hobots 1.0.0 → 1.1.1

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`), `resolveConfig`, `DEFAULT_BASE_URL` 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,18 @@
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 `resolveConfig({ clientSecret }): ResolvedConfig` — resolve a credencial nos endpoints (constante `DEFAULT_BASE_URL = 'https://api.hobots.app'`, em `src/config.ts`; em desenvolvimento a env `HOBOTS_API_URL` sobrescreve a URL base):
33
35
 
34
- Classes exportadas: `Client`, `Scope`, `Transaction`, `Span`. Função `parseDsn(dsn): ParsedDsn`.
36
+ ```ts
37
+ interface ResolvedConfig {
38
+ key: string; // Client Secret do projeto (vai no Authorization: Bearer, nunca na URL)
39
+ baseUrl: string; // DEFAULT_BASE_URL, ou HOBOTS_API_URL em desenvolvimento
40
+ ingestEndpoint: string; // <baseUrl>/api/ingest
41
+ managerBaseUrl: string; // <baseUrl>/api/manager
42
+ }
43
+ ```
35
44
 
36
45
  ### Métodos de `Transaction` / `Span`
37
46
 
@@ -43,14 +52,17 @@ Classes exportadas: `Client`, `Scope`, `Transaction`, `Span`. Função `parseDsn
43
52
 
44
53
  ## Manager — funções
45
54
 
55
+ Habilitado com `manager: true` (ou `ManagerOptions`) no `init()` único. Todas exportadas do import raiz `hobots`:
56
+
46
57
  | Função | Assinatura |
47
58
  | --- | --- |
48
- | `init` | `init(options: ManagerInitOptions): void` |
49
59
  | `register` | `register<P, R>(task: string, handler: ManagerTaskHandler<P, R>): void` |
50
- | `start` | `start(): void` |
51
- | `stop` | `stop(timeoutMs?: number): Promise<boolean>` |
60
+ | `start` | `start(): void` — lança se o `init()` não configurou `manager` ou sem tasks registradas |
61
+ | `stop` | `stop(timeoutMs?: number): Promise<boolean>` — para só o Manager; a telemetria continua |
52
62
 
53
- Classes/erros exportados: `ManagerAgent`, `ManagerApi`, `ManagerApiError`. Função `parseManagerDsn(dsn): ParsedManagerDsn`.
63
+ Classes/erros exportados: `ManagerAgent`, `ManagerApi`, `ManagerApiError`.
64
+
65
+ 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
66
 
55
67
  ---
56
68
 
@@ -60,16 +72,17 @@ Classes/erros exportados: `ManagerAgent`, `ManagerApi`, `ManagerApiError`. Funç
60
72
 
61
73
  | Opção | Tipo | Default | Descrição |
62
74
  | --- | --- | --- | --- |
63
- | `dsn` | `string` | — | **obrigatório**; `<origin>/<publicKey>/<projectId>` |
75
+ | `clientSecret` | `string` | — | **obrigatório**; o Client Secret do projeto (`hb_<64 hex>`), copiado em **Configurações → Projetos** |
64
76
  | `environment` | `string` | `'production'` | ambiente lógico |
65
77
  | `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 |
78
+ | `instanceId` | `string` | **obrigatório** | identidade única da instância; heartbeat do Monitor e agente do Manager convergem nela |
67
79
  | `tags` | `Record<string,string>` | — | tags aplicadas a todos os eventos |
68
80
  | `sampleRate` | `number` | `1` | amostragem de erros (0..1) |
69
81
  | `tracesSampleRate` | `number` | `1` | amostragem de transactions (0..1) |
70
82
  | `maxBreadcrumbs` | `number` | `100` | tamanho do buffer de breadcrumbs |
71
83
  | `captureUnhandled` | `boolean` | `true` | handlers globais de erro |
72
84
  | `heartbeat` | `boolean \| HeartbeatOptions` | `false` | heartbeat periódico |
85
+ | `manager` | `boolean \| ManagerOptions` | `false` | habilita o agente Manager (`true` usa defaults) |
73
86
  | `beforeSend` | `(event) => event \| null` | — | edita/descarta evento (retorne `null` para descartar) |
74
87
  | `debug` | `boolean` | `false` | loga atividade do SDK |
75
88
  | `flushIntervalMs` | `number` | `2000` | intervalo do flush automático do transport |
@@ -80,8 +93,7 @@ Classes/erros exportados: `ManagerAgent`, `ManagerApi`, `ManagerApiError`. Funç
80
93
  | Opção | Tipo | Default | Descrição |
81
94
  | --- | --- | --- | --- |
82
95
  | `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 |
96
+ | `name` | `string` | o `instanceId` | nome amigável no dashboard |
85
97
  | `metadata` | `Record<string,unknown>` | — | metadados anexados ao batimento |
86
98
 
87
99
  ### CaptureHint
@@ -113,23 +125,23 @@ Classes/erros exportados: `ManagerAgent`, `ManagerApi`, `ManagerApiError`. Funç
113
125
  | `schedule` | `string` | expressão cron (upsert do monitor) |
114
126
  | `timezone` | `string` | fuso do `schedule` |
115
127
 
116
- ### ManagerInitOptions
128
+ ### ManagerOptions
129
+
130
+ Valor do campo `manager` do `InitOptions`. O Client Secret, o `debug` e a identidade (`instanceId`) vêm do `init()`.
117
131
 
118
132
  | Opção | Tipo | Default | Descrição |
119
133
  | --- | --- | --- | --- |
120
- | `dsn` | `string` | — | **obrigatório**, com a **manager key** (não a chave de ingestão) |
121
134
  | `pollIntervalMs` | `number` | `5000` | intervalo entre polls sem trabalho (mínimo `1000`) |
122
135
  | `concurrency` | `number` | `1` | runs simultâneas (clamp 1..5) |
123
- | `agentName` | `string` | hostname | nome exibido no dashboard |
124
136
  | `taskTimeoutMs` | `number` | — | timeout client-side por run (não mata o handler; servidor é o backstop) |
125
137
  | `logFlushIntervalMs` | `number` | `2000` | intervalo do envio incremental de logs |
126
- | `debug` | `boolean` | `false` | loga atividade do agente |
127
138
 
128
139
  ### ManagerTaskHandler / RunContext
129
140
 
130
141
  ```ts
131
- type ManagerTaskHandler<P = Record<string, unknown>, R = unknown> =
132
- (params: P, ctx: RunContext) => R | Promise<R>;
142
+ // o retorno do handler é ignorado — nada além de status/logs/erro é enviado ao servidor
143
+ type ManagerTaskHandler<P = Record<string, unknown>> =
144
+ (params: P, ctx: RunContext) => void | Promise<void>;
133
145
 
134
146
  interface RunContext {
135
147
  runId: string;
@@ -8,39 +8,43 @@ pnpm add hobots
8
8
 
9
9
  Sem dependências de runtime. Requer **Node 18+** (usa o `fetch` global). O Monitor também funciona no browser para captura de erros.
10
10
 
11
- ## O DSN
11
+ ## O Client Secret
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.
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.
13
+ Cada projeto tem **um único Client Secret** — a mesma credencial autentica o Monitor **e** o Manager:
17
14
 
18
15
  ```
19
- Monitor: http://<publicKey>@localhost:3002/<projectId>
20
- Manager: http://<managerKey>@localhost:3002/<projectSlug>
16
+ hb_<64 hex>
17
+ ```
18
+
19
+ (Chaves antigas, 64 hex puros, continuam válidas no servidor.) 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.
20
+
21
+ O SDK fala com a API oficial `https://api.hobots.app` automaticamente — não há configuração de host no `init()`. Em desenvolvimento, aponte para outro host com a env `HOBOTS_API_URL`:
22
+
23
+ ```bash
24
+ HOBOTS_API_URL=http://localhost:3001 node meu-robo.js
21
25
  ```
22
26
 
23
- Veja [Protocolo & transporte](./protocol.md) para os endpoints que cada DSN deriva.
27
+ Veja [Protocolo & transporte](./protocol.md) para os endpoints derivados da URL base.
24
28
 
25
- ## Monitor vs Manager — qual usar
29
+ ## Monitor e Manager — qual usar
26
30
 
27
- As duas capabilities são independentes e você usa **uma, outra ou as duas**:
31
+ As duas capabilities vivem no mesmo `init()` e você usa **uma, outra ou as duas**:
28
32
 
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).
33
+ - Quer só saber se o robô quebrou, quanto demora e se está vivo? → `init({ clientSecret, heartbeat: true })`.
34
+ - Quer disparar o robô sob demanda a partir de um formulário no app? → `init({ clientSecret, manager: true })` + `register()` + `start()`.
35
+ - Ambos? Passe `heartbeat` e `manager` no **mesmo `init()`**. É o cenário do
36
+ [`full.ts`](../../hobots-sdk-demos/src/full.ts) nos demos do SDK.
33
37
 
34
- ## Primeiro `init` (Monitor)
38
+ ## O `init` único
35
39
 
36
40
  ```js
37
- import * as Monitor from 'hobots';
41
+ import * as Hobots from 'hobots';
38
42
 
39
- Monitor.init({
40
- dsn: 'http://<publicKey>@localhost:3002/<projectId>',
43
+ Hobots.init({
44
+ clientSecret: 'hb_<64 hex>',
41
45
  environment: 'production',
42
46
  release: 'robo-nfe@1.4.2',
43
- serverName: 'vm-cliente-01',
47
+ instanceId: 'vm-cliente-01', // identidade única: heartbeat + agente Manager
44
48
  heartbeat: true, // "estou vivo" a cada 30s
45
49
  });
46
50
 
@@ -49,40 +53,53 @@ Monitor.init({
49
53
  try {
50
54
  await processarNota(nota);
51
55
  } catch (error) {
52
- Monitor.captureException(error, { tags: { cliente: 'acme' } });
56
+ Hobots.captureException(error, { tags: { cliente: 'acme' } });
53
57
  throw error;
54
58
  }
55
59
  ```
56
60
 
57
- Chame `Monitor.init` **uma vez**, o mais cedo possível no processo. Detalhes em [Monitor](./monitor.md).
61
+ Chame `Hobots.init` **uma vez**, o mais cedo possível no processo. Detalhes em [Monitor](./monitor.md).
58
62
 
59
63
  ## Primeiro agente (Manager)
60
64
 
65
+ Habilite a execução sob demanda com o campo `manager` no mesmo `init()`:
66
+
61
67
  ```js
62
- import * as Manager from 'hobots/manager';
68
+ import * as Hobots from 'hobots';
69
+
70
+ Hobots.init({
71
+ clientSecret: 'hb_<64 hex>',
72
+ instanceId: 'vm-cliente-01',
73
+ manager: true, // ou { pollIntervalMs, concurrency, ... }
74
+ });
63
75
 
64
- Manager.init({ dsn: 'http://<managerKey>@localhost:3002/<projectSlug>', agentName: 'VM Cliente 01' });
65
- Manager.register('processar-nfe', async (params, ctx) => {
76
+ Hobots.register('processar-nfe', async (params, ctx) => {
66
77
  ctx.log('info', `processando lote ${params.lote}…`);
67
78
  return { processadas: 12 };
68
79
  });
69
- Manager.start(); // mantém o processo vivo até stop()
80
+ Hobots.start(); // mantém o processo vivo até stop()/close()
70
81
  ```
71
82
 
83
+ > 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.
84
+
72
85
  Detalhes (cancelamento, concorrência, idempotência) em [Manager](./manager.md).
73
86
 
74
87
  ## Encerramento
75
88
 
76
- O **Monitor** não segura o processo (timers `unref`'d), mas faça o flush final para não perder eventos em buffer:
89
+ `close()` encerra tudo na ordem certa: para o Manager (aguardando a run atual), o heartbeat e faz o flush final do transport.
90
+
91
+ 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
92
 
78
93
  ```js
79
- await Monitor.close(2000);
94
+ await Hobots.close(2000);
80
95
  ```
81
96
 
82
- O **Manager** segura o processo por design. Encerre de forma graciosa aguardando a run atual:
97
+ Com o Manager, `start()` segura o processo por design. Encerre de forma graciosa aguardando a run atual:
83
98
 
84
99
  ```js
85
100
  process.on('SIGINT', () => {
86
- void Manager.stop(15_000).then(() => process.exit(0));
101
+ void Hobots.close(15_000).then(() => process.exit(0));
87
102
  });
88
103
  ```
104
+
105
+ 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 o mesmo Client Secret, 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
+ clientSecret: 'hb_<64 hex>',
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
+ clientSecret: 'hb_<64 hex>',
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({
122
- dsn,
121
+ Hobots.init({
122
+ clientSecret,
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
+ ## O Client Secret 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 **um único Client Secret**, o mesmo para as duas capabilities:
13
10
 
14
- - Monitor: `parseDsn(dsn) → { publicKey, projectId, endpoint }`.
15
- - Manager: `parseManagerDsn(dsn) → { key, projectSlug, origin, baseUrl }`.
11
+ ```
12
+ hb_<64 hex>
13
+ ```
14
+
15
+ | Canal | Endpoint | Autenticação |
16
+ | --- | --- | --- |
17
+ | Monitor (ingestão) | `POST <baseUrl>/api/ingest` | `Authorization: Bearer <clientSecret>` |
18
+ | Manager (execução) | `<baseUrl>/api/manager/*` | `Authorization: Bearer <clientSecret>` |
19
+
20
+ `resolveConfig({ clientSecret }) → { key, baseUrl, ingestEndpoint, managerBaseUrl }` (em `src/config.ts`, com a constante `DEFAULT_BASE_URL = 'https://api.hobots.app'`; em desenvolvimento a env `HOBOTS_API_URL` sobrescreve a URL base). A credencial é **secreta** (formato `hb_<64 hex>`; chaves antigas de 64 hex puros continuam válidas no servidor) e viaja **só no header** `Authorization: Bearer` — nunca na URL da requisição.
21
+
22
+ > As DSNs foram **descontinuadas** em favor do Client Secret — tanto a antiga do Monitor (public key na URL, `POST /api/ingest/<key>`, que responde **410** e o transport descarta sem retry) quanto a de key-na-URL (`https://<secretKey>@host/<slug>`). Copie o Client Secret em **Configurações → Projetos** 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 `<baseUrl>/api/manager`, ambos com `Authorization: Bearer <clientSecret>` (a credencial ú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.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. 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
- "types": "./dist/manager/protocol.d.ts",
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
+ }