hobots 0.3.1 → 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.
Files changed (102) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +389 -0
  3. package/dist/index.cjs +1203 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +312 -0
  6. package/dist/index.d.ts +312 -0
  7. package/dist/index.js +1180 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/manager/protocol.cjs +19 -0
  10. package/dist/manager/protocol.cjs.map +1 -0
  11. package/dist/manager/protocol.d.cts +72 -0
  12. package/dist/manager/protocol.d.ts +72 -0
  13. package/dist/manager/protocol.js +1 -0
  14. package/dist/manager/protocol.js.map +1 -0
  15. package/dist/protocol.cjs +19 -0
  16. package/dist/protocol.cjs.map +1 -0
  17. package/dist/protocol.d.cts +129 -0
  18. package/dist/protocol.d.ts +129 -0
  19. package/dist/protocol.js +1 -0
  20. package/dist/protocol.js.map +1 -0
  21. package/docs/README.md +41 -0
  22. package/docs/api-reference.md +153 -0
  23. package/docs/getting-started.md +103 -0
  24. package/docs/manager.md +95 -0
  25. package/docs/monitor.md +138 -0
  26. package/docs/protocol.md +101 -0
  27. package/package.json +46 -3
  28. package/auth/index.d.ts +0 -10
  29. package/csv/index.d.ts +0 -9
  30. package/datasets/index.d.ts +0 -2
  31. package/db/drizzle.config.d.ts +0 -8
  32. package/db/index.d.ts +0 -9
  33. package/db/migrate.d.ts +0 -3
  34. package/db/schema.d.ts +0 -215
  35. package/db/storages/base.d.ts +0 -23
  36. package/db/storages/index.d.ts +0 -3
  37. package/db/storages/job.d.ts +0 -233
  38. package/exceptions/break.d.ts +0 -6
  39. package/exceptions/ignore.d.ts +0 -6
  40. package/exceptions/index.d.ts +0 -4
  41. package/exceptions/warning.d.ts +0 -6
  42. package/foreach/foreach.d.ts +0 -7
  43. package/foreach/index.d.ts +0 -5
  44. package/foreach/item.d.ts +0 -13
  45. package/hobots/samples/bot.d.ts +0 -2
  46. package/hobots/samples/index.d.ts +0 -2
  47. package/hobots/src/auth/index.d.ts +0 -10
  48. package/hobots/src/csv/index.d.ts +0 -9
  49. package/hobots/src/datasets/index.d.ts +0 -2
  50. package/hobots/src/db/drizzle.config.d.ts +0 -8
  51. package/hobots/src/db/index.d.ts +0 -9
  52. package/hobots/src/db/migrate.d.ts +0 -3
  53. package/hobots/src/db/schema.d.ts +0 -215
  54. package/hobots/src/db/storages/base.d.ts +0 -23
  55. package/hobots/src/db/storages/index.d.ts +0 -3
  56. package/hobots/src/db/storages/job.d.ts +0 -233
  57. package/hobots/src/exceptions/break.d.ts +0 -6
  58. package/hobots/src/exceptions/ignore.d.ts +0 -6
  59. package/hobots/src/exceptions/index.d.ts +0 -4
  60. package/hobots/src/exceptions/warning.d.ts +0 -6
  61. package/hobots/src/foreach/foreach.d.ts +0 -7
  62. package/hobots/src/foreach/index.d.ts +0 -5
  63. package/hobots/src/foreach/item.d.ts +0 -13
  64. package/hobots/src/index.d.ts +0 -9
  65. package/hobots/src/reports/data-report.d.ts +0 -9
  66. package/hobots/src/reports/index.d.ts +0 -3
  67. package/hobots/src/reports/job-report.d.ts +0 -22
  68. package/hobots/src/setup.d.ts +0 -20
  69. package/hobots/src/sheets/index.d.ts +0 -13
  70. package/hobots/src/types/hobots-types.d.ts +0 -20
  71. package/hobots/src/utils/date.d.ts +0 -117
  72. package/hobots/src/utils/dist.d.ts +0 -8
  73. package/hobots/src/utils/garbage.d.ts +0 -2
  74. package/hobots/src/utils/index.d.ts +0 -10
  75. package/hobots/src/utils/logger.d.ts +0 -21
  76. package/hobots/src/utils/manifest.d.ts +0 -2
  77. package/hobots/src/utils/path.d.ts +0 -13
  78. package/hobots/src/utils/publish.d.ts +0 -2
  79. package/hobots/src/utils/retry.d.ts +0 -5
  80. package/hobots/src/utils/text.d.ts +0 -74
  81. package/hobots/src/utils/timer.d.ts +0 -3
  82. package/index.d.ts +0 -9
  83. package/index.js +0 -131866
  84. package/index.js.map +0 -1
  85. package/reports/data-report.d.ts +0 -9
  86. package/reports/index.d.ts +0 -3
  87. package/reports/job-report.d.ts +0 -22
  88. package/setup.d.ts +0 -20
  89. package/sheets/index.d.ts +0 -13
  90. package/types/hobots-types.d.ts +0 -20
  91. package/utils/date.d.ts +0 -117
  92. package/utils/dist.d.ts +0 -8
  93. package/utils/garbage.d.ts +0 -2
  94. package/utils/index.d.ts +0 -10
  95. package/utils/logger.d.ts +0 -21
  96. package/utils/manifest.d.ts +0 -2
  97. package/utils/path.d.ts +0 -13
  98. package/utils/publish.d.ts +0 -2
  99. package/utils/retry.d.ts +0 -5
  100. package/utils/src/path.d.ts +0 -14
  101. package/utils/text.d.ts +0 -74
  102. package/utils/timer.d.ts +0 -3
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/docs/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # Documentação — hobots
2
+
3
+ SDK JavaScript da **Hobots** para instrumentar funcionários digitais (RPAs) e integrações. Cliente HTTP puro, sem dependências de runtime, para Node 18+ (o Monitor também roda no browser para captura de erros).
4
+
5
+ O SDK expõe **duas capabilities** autenticadas pela **mesma DSN única do projeto**, configuradas em **um único `init()`** com **um único import** (`import * as Hobots from 'hobots'`):
6
+
7
+ | Capability | Como habilita | Para quê |
8
+ | --- | --- | --- |
9
+ | **Monitor** | sempre ativo no `init()` | Telemetria que o programa **emite** sozinho: erros com stack trace, performance (transactions/spans), check-ins de cron e heartbeats de uptime. Instala handlers globais de erro; os timers são `unref`'d, então nunca seguram o processo vivo. |
10
+ | **Manager** | `manager: true` (ou opções) no `init()` | Execução **sob demanda**: o agente faz polling no app, executa handlers registrados e reporta status/logs. `start()` **mantém o processo vivo** até `stop()`/`close()`. |
11
+
12
+ ## Páginas
13
+
14
+ - [Getting started](./getting-started.md) — instalação, DSN, o `init` único, Monitor e Manager.
15
+ - [Monitor](./monitor.md) — erros, breadcrumbs, escopo, transactions/spans, crons e heartbeat.
16
+ - [Manager](./manager.md) — registro de handlers, `RunContext`, cancelamento, concorrência, idempotência.
17
+ - [Referência de API](./api-reference.md) — todas as funções e tabelas de opções com defaults.
18
+ - [Protocolo & transporte](./protocol.md) — a DSN única, endpoints, envelope, retries e onde vive o claim atômico.
19
+
20
+ ## Exemplo mínimo
21
+
22
+ ```js
23
+ import * as Hobots from 'hobots';
24
+
25
+ // A DSN única do projeto — copie em Configurações → Agentes no app
26
+ const dsn = 'http://<secretKey>@localhost:3002/<projectSlug>';
27
+
28
+ // Um init só: telemetria sempre ativa; manager habilita a execução sob demanda.
29
+ // O instanceId é a identidade única — o robô aparece como um agente só no painel.
30
+ Hobots.init({ dsn, instanceId: 'vm-cliente-01', heartbeat: true, manager: true });
31
+
32
+ Hobots.register('processar-nfe', async (params, ctx) => {
33
+ ctx.log('info', `processando lote ${params.lote}…`);
34
+ return { processadas: 12 };
35
+ });
36
+ Hobots.start(); // mantém o processo vivo aguardando execuções
37
+ ```
38
+
39
+ > Exemplos runnable de toda a API pública (incluindo as duas capabilities juntas em
40
+ > [`full.ts`](../../hobots-sdk-demos/src/full.ts)) estão em
41
+ > [`packages/hobots-sdk-demos`](../../hobots-sdk-demos).
@@ -0,0 +1,153 @@
1
+ # Referência de API
2
+
3
+ ## Entry points
4
+
5
+ | Import | Conteúdo |
6
+ | --- | --- |
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
+
12
+ ---
13
+
14
+ ## Monitor — funções
15
+
16
+ | Função | Assinatura | Retorno |
17
+ | --- | --- | --- |
18
+ | `init` | `init(options: InitOptions): void` | — |
19
+ | `getClient` | `getClient(): Client \| undefined` | cliente atual |
20
+ | `captureException` | `captureException(error: unknown, hint?: CaptureHint)` | `string \| undefined` (event_id) |
21
+ | `captureMessage` | `captureMessage(message: string, level?: Severity, hint?: CaptureHint)` | `string \| undefined` |
22
+ | `addBreadcrumb` | `addBreadcrumb(crumb: BreadcrumbInput): void` | — |
23
+ | `setTag` / `setTags` | `setTag(key, value)` · `setTags(tags)` | — |
24
+ | `setUser` | `setUser(user: EventUser \| null): void` | — |
25
+ | `setContext` | `setContext(name: string, ctx: Record<string,unknown> \| null): void` | — |
26
+ | `setExtra` | `setExtra(key: string, value: unknown): void` | — |
27
+ | `withScope` | `withScope<T>(cb: (scope: Scope) => T): T` | retorno do callback |
28
+ | `startTransaction` | `startTransaction(name: string, op?: string): Transaction` | transaction |
29
+ | `checkIn` | `checkIn(options: CheckInOptions): string \| undefined` | check_in_id |
30
+ | `withMonitor` | `withMonitor<T>(slug: string, fn: () => T \| Promise<T>, options?: WithMonitorOptions): Promise<T>` | retorno de `fn` |
31
+ | `flush` | `flush(timeoutMs?: number): Promise<boolean>` | enviou tudo? |
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:
35
+
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
+ ```
45
+
46
+ ### Métodos de `Transaction` / `Span`
47
+
48
+ - `Transaction`: `startChild(op?, description?) → Span`, `setTag(k, v)`, `setStatus(s)`, `setName(n)`, `finish()`.
49
+ - `Span`: `startChild(op?, description?) → Span`, `setStatus(s)`, `setData(k, v)`, `finish()`, `toJSON()`.
50
+ - `Scope`: `setTag/setTags/setUser/setContext/setExtra/setExtras/setLevel/setFingerprint/addBreadcrumb/clearBreadcrumbs/clone` (todos encadeáveis).
51
+
52
+ ---
53
+
54
+ ## Manager — funções
55
+
56
+ Habilitado com `manager: true` (ou `ManagerOptions`) no `init()` único. Todas exportadas do import raiz `hobots`:
57
+
58
+ | Função | Assinatura |
59
+ | --- | --- |
60
+ | `register` | `register<P, R>(task: string, handler: ManagerTaskHandler<P, R>): void` |
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 |
63
+
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.
67
+
68
+ ---
69
+
70
+ ## Tabelas de opções
71
+
72
+ ### InitOptions
73
+
74
+ | Opção | Tipo | Default | Descrição |
75
+ | --- | --- | --- | --- |
76
+ | `dsn` | `string` | — | **obrigatório**; a DSN única do projeto: `https://<secretKey>@host/<projectSlug>` |
77
+ | `environment` | `string` | `'production'` | ambiente lógico |
78
+ | `release` | `string` | — | versão do programa, ex. `robo-nfe@1.4.2` |
79
+ | `instanceId` | `string` | **obrigatório** | identidade única da instância; heartbeat do Monitor e agente do Manager convergem nela |
80
+ | `tags` | `Record<string,string>` | — | tags aplicadas a todos os eventos |
81
+ | `sampleRate` | `number` | `1` | amostragem de erros (0..1) |
82
+ | `tracesSampleRate` | `number` | `1` | amostragem de transactions (0..1) |
83
+ | `maxBreadcrumbs` | `number` | `100` | tamanho do buffer de breadcrumbs |
84
+ | `captureUnhandled` | `boolean` | `true` | handlers globais de erro |
85
+ | `heartbeat` | `boolean \| HeartbeatOptions` | `false` | heartbeat periódico |
86
+ | `manager` | `boolean \| ManagerOptions` | `false` | habilita o agente Manager (`true` usa defaults) |
87
+ | `beforeSend` | `(event) => event \| null` | — | edita/descarta evento (retorne `null` para descartar) |
88
+ | `debug` | `boolean` | `false` | loga atividade do SDK |
89
+ | `flushIntervalMs` | `number` | `2000` | intervalo do flush automático do transport |
90
+ | `maxBatchSize` | `number` | `10` | itens por envelope antes do flush imediato |
91
+
92
+ ### HeartbeatOptions
93
+
94
+ | Opção | Tipo | Default | Descrição |
95
+ | --- | --- | --- | --- |
96
+ | `intervalMs` | `number` | `30000` | intervalo entre batimentos |
97
+ | `name` | `string` | o `instanceId` | nome amigável no dashboard |
98
+ | `metadata` | `Record<string,unknown>` | — | metadados anexados ao batimento |
99
+
100
+ ### CaptureHint
101
+
102
+ | Campo | Tipo | Descrição |
103
+ | --- | --- | --- |
104
+ | `level` | `Severity` | nível do evento |
105
+ | `tags` | `Record<string,string>` | tags só deste evento |
106
+ | `extra` | `Record<string,unknown>` | dados extras |
107
+ | `fingerprint` | `string[]` | agrupamento manual |
108
+
109
+ `Severity = 'fatal' | 'error' | 'warning' | 'info' | 'debug'`.
110
+
111
+ ### CheckInOptions
112
+
113
+ | Campo | Tipo | Descrição |
114
+ | --- | --- | --- |
115
+ | `monitorSlug` | `string` | **obrigatório**; identifica o monitor |
116
+ | `status` | `'in_progress' \| 'ok' \| 'error'` | **obrigatório** |
117
+ | `checkInId` | `string` | reutilize o id do `in_progress` para fechar a execução |
118
+ | `durationMs` | `number` | duração medida |
119
+ | `schedule` | `string` | expressão cron — cria/atualiza o monitor no servidor (upsert) |
120
+ | `timezone` | `string` | fuso do `schedule` |
121
+
122
+ ### WithMonitorOptions
123
+
124
+ | Campo | Tipo | Descrição |
125
+ | --- | --- | --- |
126
+ | `schedule` | `string` | expressão cron (upsert do monitor) |
127
+ | `timezone` | `string` | fuso do `schedule` |
128
+
129
+ ### ManagerOptions
130
+
131
+ Valor do campo `manager` do `InitOptions`. A DSN, o `debug` e a identidade (`instanceId`) vêm do `init()`.
132
+
133
+ | Opção | Tipo | Default | Descrição |
134
+ | --- | --- | --- | --- |
135
+ | `pollIntervalMs` | `number` | `5000` | intervalo entre polls sem trabalho (mínimo `1000`) |
136
+ | `concurrency` | `number` | `1` | runs simultâneas (clamp 1..5) |
137
+ | `taskTimeoutMs` | `number` | — | timeout client-side por run (não mata o handler; servidor é o backstop) |
138
+ | `logFlushIntervalMs` | `number` | `2000` | intervalo do envio incremental de logs |
139
+
140
+ ### ManagerTaskHandler / RunContext
141
+
142
+ ```ts
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>;
146
+
147
+ interface RunContext {
148
+ runId: string;
149
+ task: string;
150
+ log(level: 'debug' | 'info' | 'warning' | 'error', message: string): void;
151
+ signal: AbortSignal;
152
+ }
153
+ ```
@@ -0,0 +1,103 @@
1
+ # Getting started
2
+
3
+ ## Instalação
4
+
5
+ ```bash
6
+ pnpm add hobots
7
+ ```
8
+
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
+
11
+ ## O DSN
12
+
13
+ Cada projeto tem **uma única DSN** — a mesma credencial autentica o Monitor **e** o Manager:
14
+
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.
20
+
21
+ ```
22
+ http://<secretKey>@localhost:3002/<projectSlug>
23
+ ```
24
+
25
+ Veja [Protocolo & transporte](./protocol.md) para os endpoints que a DSN deriva.
26
+
27
+ ## Monitor e Manager — qual usar
28
+
29
+ As duas capabilities vivem no mesmo `init()` e você usa **uma, outra ou as duas**:
30
+
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.
35
+
36
+ ## O `init` único
37
+
38
+ ```js
39
+ import * as Hobots from 'hobots';
40
+
41
+ Hobots.init({
42
+ dsn: 'http://<secretKey>@localhost:3002/<projectSlug>',
43
+ environment: 'production',
44
+ release: 'robo-nfe@1.4.2',
45
+ instanceId: 'vm-cliente-01', // identidade única: heartbeat + agente Manager
46
+ heartbeat: true, // "estou vivo" a cada 30s
47
+ });
48
+
49
+ // Erros não tratados são capturados automaticamente.
50
+ // Captura manual:
51
+ try {
52
+ await processarNota(nota);
53
+ } catch (error) {
54
+ Hobots.captureException(error, { tags: { cliente: 'acme' } });
55
+ throw error;
56
+ }
57
+ ```
58
+
59
+ Chame `Hobots.init` **uma vez**, o mais cedo possível no processo. Detalhes em [Monitor](./monitor.md).
60
+
61
+ ## Primeiro agente (Manager)
62
+
63
+ Habilite a execução sob demanda com o campo `manager` no mesmo `init()`:
64
+
65
+ ```js
66
+ import * as Hobots from 'hobots';
67
+
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) => {
75
+ ctx.log('info', `processando lote ${params.lote}…`);
76
+ return { processadas: 12 };
77
+ });
78
+ Hobots.start(); // mantém o processo vivo até stop()/close()
79
+ ```
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
+
83
+ Detalhes (cancelamento, concorrência, idempotência) em [Manager](./manager.md).
84
+
85
+ ## Encerramento
86
+
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:
90
+
91
+ ```js
92
+ await Hobots.close(2000);
93
+ ```
94
+
95
+ Com o Manager, `start()` segura o processo por design. Encerre de forma graciosa aguardando a run atual:
96
+
97
+ ```js
98
+ process.on('SIGINT', () => {
99
+ void Hobots.close(15_000).then(() => process.exit(0));
100
+ });
101
+ ```
102
+
103
+ Para parar de aceitar execuções **sem** derrubar a telemetria, use `Hobots.stop(timeoutMs?)`.
@@ -0,0 +1,95 @@
1
+ # Manager
2
+
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.
4
+
5
+ ## Ciclo de vida
6
+
7
+ ```js
8
+ import * as Hobots from 'hobots';
9
+
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
+ ctx.log('info', `processando lote ${params.lote}…`);
18
+ if (ctx.signal.aborted) return; // cancelamento cooperativo
19
+ ctx.log('info', '12 notas processadas');
20
+ });
21
+
22
+ Hobots.start(); // começa o polling e MANTÉM O PROCESSO VIVO
23
+
24
+ process.on('SIGINT', () => {
25
+ void Hobots.close(15_000).then(() => process.exit(0));
26
+ });
27
+ ```
28
+
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.
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.
38
+
39
+ ## O handler e o `RunContext`
40
+
41
+ ```ts
42
+ type ManagerTaskHandler<P = Record<string, unknown>> =
43
+ (params: P, ctx: RunContext) => void | Promise<void>;
44
+
45
+ interface RunContext {
46
+ runId: string;
47
+ task: string;
48
+ log(level: 'debug' | 'info' | 'warning' | 'error', message: string): void;
49
+ signal: AbortSignal;
50
+ }
51
+ ```
52
+
53
+ - **`params`** — os dados do formulário configurado no app (o JSON validado).
54
+ - **`ctx.log(level, message)`** — logs enviados **incrementalmente** ao app (PATCHes com throttle), visíveis na run em tempo quase real.
55
+ - **`ctx.signal`** — `AbortSignal` que dispara quando a run é **cancelada no app**, em **timeout** ou no **shutdown**. Cancelamento é cooperativo (veja abaixo).
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.
57
+ - **Erro lançado** — a run é reportada como `failed` com tipo/mensagem/stack (mensagem cap 10k, stack 20k).
58
+
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.
62
+
63
+ ## Cancelamento cooperativo
64
+
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:
66
+
67
+ ```js
68
+ Hobots.register('processar-nfe', async (params, ctx) => {
69
+ for (const nota of params.notas) {
70
+ if (ctx.signal.aborted) {
71
+ ctx.log('warning', 'cancelamento detectado — parando');
72
+ return;
73
+ }
74
+ await processar(nota);
75
+ }
76
+ });
77
+ ```
78
+
79
+ ## Idempotência (at-least-once)
80
+
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).
82
+
83
+ ## Concorrência
84
+
85
+ `concurrency` (default 1, máx 5) controla quantas runs o agente executa em paralelo. Com 1, as runs são sequenciais. O agente só reivindica o que consegue rodar (`max = concurrency - runs ativas`) e entra em **drain mode**: enquanto vêm runs, re-pesquisa imediatamente; quando não vem nada, espera `pollIntervalMs`.
86
+
87
+ ## Timeout por run
88
+
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.
90
+
91
+ Todas as opções em [Referência de API → ManagerOptions](./api-reference.md#manageroptions).
92
+
93
+ ## Como o claim funciona (resumo)
94
+
95
+ O SDK é um cliente HTTP: ele faz `POST /api/manager/poll` e o **servidor** reivindica as runs pendentes de forma atômica com `FOR UPDATE SKIP LOCKED` — dois agentes nunca pegam a mesma run. Detalhes em [Protocolo & transporte](./protocol.md).
@@ -0,0 +1,138 @@
1
+ # Monitor
2
+
3
+ Capability de telemetria: erros, performance, crons e uptime. Sempre ativa no `init()` único:
4
+
5
+ ```js
6
+ import * as Hobots from 'hobots';
7
+ ```
8
+
9
+ Todas as funções abaixo são no-op seguros se `Hobots.init()` não foi chamado (com aviso no console). Assinaturas completas em [Referência de API](./api-reference.md).
10
+
11
+ ## init
12
+
13
+ ```js
14
+ Hobots.init({
15
+ dsn: 'http://<secretKey>@localhost:3002/<projectSlug>',
16
+ environment: 'production',
17
+ release: 'robo-nfe@1.4.2',
18
+ instanceId: 'vm-cliente-01',
19
+ tags: { squad: 'automacoes' },
20
+ heartbeat: true,
21
+ });
22
+ ```
23
+
24
+ Cria o cliente, instala handlers globais de erro (a menos que `captureUnhandled: false`) e inicia o heartbeat se configurado. Chamar de novo substitui o cliente. Todos os campos em [Referência de API → InitOptions](./api-reference.md#initoptions).
25
+
26
+ ## Captura de erros e mensagens
27
+
28
+ ```js
29
+ // exceções
30
+ Hobots.captureException(error, {
31
+ tags: { cliente: 'acme' },
32
+ level: 'error',
33
+ fingerprint: ['sefaz-timeout'], // agrupa manualmente; sem isso o servidor agrupa pelo stack
34
+ });
35
+
36
+ // mensagens sem exceção
37
+ Hobots.captureMessage('Lote processado com sucesso', 'info');
38
+ ```
39
+
40
+ Ambas retornam o `event_id` (ou `undefined` se não inicializado). O segundo argumento é um [`CaptureHint`](./api-reference.md#capturehint) (`level`, `tags`, `extra`, `fingerprint`).
41
+
42
+ Erros **não tratados** (`uncaughtException` / `unhandledRejection`) são capturados e flushados automaticamente antes do processo sair — desligue com `captureUnhandled: false`.
43
+
44
+ ## Breadcrumbs
45
+
46
+ Trilha do que aconteceu antes do erro; enviada junto do próximo evento.
47
+
48
+ ```js
49
+ Hobots.addBreadcrumb({ category: 'nfe', message: 'Baixou XML da nota 123', level: 'info' });
50
+ ```
51
+
52
+ O buffer é limitado por `maxBreadcrumbs` (default 100, mais antigos descartados).
53
+
54
+ ## Escopo: tags, usuário, contexto, extras
55
+
56
+ Dados anexados a **todos** os eventos seguintes:
57
+
58
+ ```js
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
+ ```
65
+
66
+ Para um escopo **isolado** (sem vazar para o global), use `withScope`:
67
+
68
+ ```js
69
+ Hobots.withScope((scope) => {
70
+ scope.setTag('lote', '128').setFingerprint(['reprocessamento']);
71
+ Hobots.captureMessage('Reprocessando lote 128');
72
+ });
73
+ ```
74
+
75
+ ## Performance: transactions e spans
76
+
77
+ ```js
78
+ const tx = Hobots.startTransaction('processar-lote', 'rpa.job');
79
+
80
+ const span = tx.startChild('http.client', 'GET /api/notas');
81
+ await api.buscarNotas();
82
+ span.setData('notas', 42).finish();
83
+
84
+ const filho = span.startChild('db.query', 'INSERT INTO notas'); // spans aninham
85
+ await gravar();
86
+ filho.finish();
87
+
88
+ tx.setStatus('ok'); // ou 'error'
89
+ tx.finish(); // só aqui a transaction é enviada (respeitando tracesSampleRate)
90
+ ```
91
+
92
+ - `startChild(op?, description?)` existe tanto em `Transaction` quanto em `Span` (aninhamento arbitrário).
93
+ - Métodos encadeáveis: `setStatus`, `setData` (span), `setTag`/`setName` (transaction).
94
+ - `finish()` na transaction fecha spans abertos e envia o payload; nada é enviado se ela não foi amostrada.
95
+
96
+ ## Crons (check-ins)
97
+
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
+
100
+ ```js
101
+ await Hobots.withMonitor(
102
+ 'sync-notas',
103
+ () => executarSync(),
104
+ { schedule: '0 */2 * * *', timezone: 'America/Fortaleza' }
105
+ );
106
+ ```
107
+
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
+
110
+ ```js
111
+ const id = Hobots.checkIn({ monitorSlug: 'sync-notas', status: 'in_progress' });
112
+ // … trabalho …
113
+ Hobots.checkIn({ monitorSlug: 'sync-notas', status: 'ok', checkInId: id, durationMs: 1234 });
114
+ ```
115
+
116
+ ## Heartbeat (uptime)
117
+
118
+ Bate "estou vivo" periodicamente; o servidor acusa o robô **offline** quando os batimentos param.
119
+
120
+ ```js
121
+ Hobots.init({
122
+ dsn,
123
+ heartbeat: { intervalMs: 30_000, name: 'Robô NFe — VM 01' },
124
+ });
125
+ ```
126
+
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.
130
+
131
+ ## Flush e close
132
+
133
+ ```js
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
136
+ ```
137
+
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).
@@ -0,0 +1,101 @@
1
+ # Protocolo & transporte
2
+
3
+ O SDK é um **cliente HTTP puro**, sem dependências de runtime: ele só fala com o app da Hobots por HTTP. Não abre conexão com banco e **não** roda `SKIP LOCKED` — a reivindicação atômica de runs é server-side (veja o fim desta página).
4
+
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
+
7
+ ## A DSN e os endpoints derivados
8
+
9
+ Cada projeto tem **uma única DSN secreta**, a mesma para as duas capabilities:
10
+
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.
23
+
24
+ ## Monitor — ingestão
25
+
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`.
27
+
28
+ ```jsonc
29
+ {
30
+ "sdk": { "name": "hobots", "version": "1.0.0" },
31
+ "sent_at": "2024-01-01T00:00:00.000Z",
32
+ "items": [
33
+ { "type": "event", "payload": { /* EventPayload */ } },
34
+ { "type": "transaction", "payload": { /* TransactionPayload */ } },
35
+ { "type": "check_in", "payload": { /* CheckInPayload */ } },
36
+ { "type": "heartbeat", "payload": { /* HeartbeatPayload */ } }
37
+ ]
38
+ }
39
+ ```
40
+
41
+ Tipos de item: `event`, `transaction`, `check_in`, `heartbeat` (definidos em `hobots/protocol`).
42
+
43
+ **Flush & retries:** o transport flusha por `flushIntervalMs` (default 2s) ou ao atingir `maxBatchSize` (default 10). Ladder de retry `[1s, 4s, 16s]`; respostas 4xx (exceto 429) são descartadas sem retry; a fila tem teto de 200 itens (descarta os mais antigos).
44
+
45
+ ## Manager — polling e reporte
46
+
47
+ Dois endpoints sob `<origin>/api/manager`, ambos com `Authorization: Bearer <secretKey>` (a chave única do projeto):
48
+
49
+ ### `POST /api/manager/poll`
50
+
51
+ Corpo `ManagerPollRequest`:
52
+
53
+ ```jsonc
54
+ {
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
+ },
62
+ "max": 1, // quantas reivindicar (1..5)
63
+ "tasks": ["processar-nfe"] // só runs destas tasks registradas
64
+ }
65
+ ```
66
+
67
+ Resposta `ManagerPollResponse` = `{ runs: ManagerRunDescriptor[] }`, cada `{ id, task, params, form, source, created_at }`.
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
+
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]`.
72
+
73
+ ### `PATCH /api/manager/runs/{id}`
74
+
75
+ Corpo `ManagerRunUpdateRequest`:
76
+
77
+ ```jsonc
78
+ {
79
+ "status": "running", // 'running' | 'succeeded' | 'failed'
80
+ "logs": [{ "ts": "…", "level": "info", "message": "…" }], // delta; o servidor concatena
81
+ "error": { "type": "Error", "message": "…", "stack": "…" } // só com 'failed'
82
+ }
83
+ ```
84
+
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).
86
+ - Ordem garantida `running → logs → terminal` (PATCHes serializados numa cadeia de promise).
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`).
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).
89
+
90
+ Ciclo de vida completo da run no servidor: `pending → claimed → running → succeeded | failed | canceled`. O agente só transiciona para `running`/`succeeded`/`failed`.
91
+
92
+ ## Onde vive o claim atômico
93
+
94
+ O `POST /api/manager/poll` no app chama `claimPendingRuns()` em
95
+ [`apps/webapp/lib/manager/claim.ts`](../../../apps/webapp/lib/manager/claim.ts): um CTE que seleciona runs `pending` vencidas com `FOR UPDATE SKIP LOCKED` e as promove a `claimed` num único `UPDATE ... RETURNING`. Isso garante que dois agentes concorrentes nunca peguem a mesma run. **Nada disso está no SDK** — ele só faz o HTTP.
96
+
97
+ ## Identidade do SDK
98
+
99
+ `SDK_NAME = 'hobots'` e `SDK_VERSION` ficam em `src/utils.ts` e vão em todo envelope (campo `sdk`) e no `agent.version` do poll.
100
+
101
+ > **Nota de manutenção:** `SDK_VERSION` é uma constante fixa e precisa ser mantida em sincronia com a `version` do `package.json` manualmente ao publicar.