hobots 2.4.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -369
- package/dist/index.cjs +1217 -852
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +218 -214
- package/dist/index.d.ts +218 -214
- package/dist/index.js +1217 -852
- package/dist/index.js.map +1 -1
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.cts +15 -2
- package/dist/protocol.d.ts +15 -2
- package/docs/README.md +16 -33
- package/docs/api-reference.md +74 -213
- package/docs/concepts.md +60 -393
- package/docs/getting-started.md +38 -84
- package/docs/protocol.md +28 -19
- package/docs/tasks.md +92 -251
- package/package.json +2 -2
- package/docs/telemetry.md +0 -162
package/README.md
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
# hobots
|
|
2
2
|
|
|
3
|
-
SDK JavaScript/TypeScript da **Hobots** para
|
|
3
|
+
SDK JavaScript/TypeScript da **Hobots** para robôs (RPAs) e integrações. **Tudo é uma task**: você registra o que o robô faz e a plataforma passa a receber logs, itens processados, progresso, prints, erros e o "estou vivo" do robô.
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
|
|
8
|
-
**Um único import e um único `init()`** cobrem as duas capabilities. Cliente HTTP puro, **sem dependências de runtime**. Requer **Node 18+** (usa o `fetch` global); a telemetria também roda no browser para captura de erros.
|
|
5
|
+
- **`hobots.register(task, handler)`** — **sob demanda**: o robô fica online aguardando **solicitações** criadas no app (formulários e agendamentos) e executa o handler a cada uma. Módulo **Solicitações**.
|
|
6
|
+
- **`hobots.start(task, handler)`** — **sempre ligado**: o robô abre a própria execução ao iniciar e roda o handler (o seu loop) dentro dela; a plataforma só recebe logs, itens e progresso. Sem formulário, sem solicitante.
|
|
9
7
|
|
|
10
8
|
---
|
|
11
9
|
|
|
@@ -17,430 +15,175 @@ npm install hobots
|
|
|
17
15
|
```
|
|
18
16
|
|
|
19
17
|
```js
|
|
20
|
-
import
|
|
18
|
+
import hobots from 'hobots'; // ou: import { hobots, register, start } from 'hobots'
|
|
21
19
|
```
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
CommonJS: `const { hobots } = require('hobots')`.
|
|
24
22
|
|
|
25
23
|
---
|
|
26
24
|
|
|
27
|
-
##
|
|
28
|
-
|
|
29
|
-
O SDK expõe **duas capabilities** autenticadas pelo **mesmo Client Secret do agente**, configuradas em um único `init()`:
|
|
30
|
-
|
|
31
|
-
| Capability | Como habilita | Para quê | Ciclo de vida |
|
|
32
|
-
| --- | --- | --- | --- |
|
|
33
|
-
| **Telemetria** | sempre ativa no `init()` | O que o programa emite sozinho: erros, transactions/etapas, heartbeat. | Timers `unref`'d — **não** segura o processo. Chame `close()` no fim. |
|
|
34
|
-
| **Tasks** | `tasks: true` (ou opções) no `init()` | Execução remota sob demanda via polling. | `start()` **segura o processo vivo** até `stop()`/`close()`. |
|
|
35
|
-
|
|
36
|
-
Você usa **uma, outra ou as duas**:
|
|
37
|
-
|
|
38
|
-
- Só saber se o robô quebrou, quanto demora e se está vivo? → `init({ clientSecret, heartbeat: true })`.
|
|
39
|
-
- Disparar o robô sob demanda a partir de um formulário no app? → `init({ clientSecret, tasks: true })` + `register()` + `start()`.
|
|
40
|
-
- Ambos? Passe `heartbeat` e `tasks` no mesmo `init()` — o `instanceId` é a identidade única, e os dois canais convergem em **um agente** no painel. Com `tasks: true`, o heartbeat é **opcional**: o próprio poll de tasks já marca a presença do agente.
|
|
41
|
-
|
|
42
|
-
```js
|
|
43
|
-
Hobots.init({
|
|
44
|
-
clientSecret: 'hb_<64 hex>',
|
|
45
|
-
instanceId: 'vm-cliente-01',
|
|
46
|
-
heartbeat: true, // telemetria: "estou vivo" a cada 30s
|
|
47
|
-
tasks: { pollIntervalMs: 2_000 }, // execução sob demanda
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
Hobots.register('processar-nfe', async (params, ctx) => {
|
|
51
|
-
ctx.log.info('começando…');
|
|
52
|
-
ctx.log.info('12 notas processadas'); // o retorno do handler é ignorado — reporte por log
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
Hobots.start(); // inicia o polling de tasks (segura o processo vivo)
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
---
|
|
59
|
-
|
|
60
|
-
## O Client Secret
|
|
61
|
-
|
|
62
|
-
Cada agente tem **um único Client Secret** — a mesma credencial autentica a telemetria **e** as tasks:
|
|
63
|
-
|
|
64
|
-
```
|
|
65
|
-
hb_<64 hex>
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
A credencial é **secreta** e viaja sempre no header `Authorization: Bearer` — nunca na URL. Copie o Client Secret no app, em **Configurações → Agentes** — os snippets de integração já vêm prontos. O SDK fala com a API oficial `https://api2.hobots.app` automaticamente; em desenvolvimento, aponte para outro host com a env `HOBOTS_API_URL` (ex.: `HOBOTS_API_URL=http://localhost:3001`).
|
|
69
|
-
|
|
70
|
-
---
|
|
71
|
-
|
|
72
|
-
## Telemetria
|
|
73
|
-
|
|
74
|
-
Todas as funções abaixo são **no-op seguras** se `Hobots.init()` não foi chamado (com aviso no console).
|
|
75
|
-
|
|
76
|
-
### `init(options)`
|
|
77
|
-
|
|
78
|
-
Inicialize **uma vez**, o mais cedo possível no processo. Cria o cliente, instala handlers globais de erro (a menos que `captureUnhandled: false`) e inicia o heartbeat se configurado.
|
|
79
|
-
|
|
80
|
-
```js
|
|
81
|
-
Hobots.init({
|
|
82
|
-
clientSecret: 'hb_<64 hex>',
|
|
83
|
-
environment: 'production',
|
|
84
|
-
release: 'robo-nfe@1.4.2',
|
|
85
|
-
instanceId: 'vm-cliente-01',
|
|
86
|
-
tags: { squad: 'automacoes' },
|
|
87
|
-
heartbeat: true, // "estou vivo" a cada 30s
|
|
88
|
-
});
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
| Opção | Tipo | Default | Descrição |
|
|
92
|
-
| --- | --- | --- | --- |
|
|
93
|
-
| `clientSecret` | `string` | **obrigatório** | o Client Secret do agente (`hb_<64 hex>`), copiado em **Configurações → Agentes** |
|
|
94
|
-
| `environment` | `string` | `'production'` | ambiente lógico |
|
|
95
|
-
| `release` | `string` | — | versão do programa, ex. `robo-nfe@1.4.2` |
|
|
96
|
-
| `instanceId` | `string` | **obrigatório** | identidade única da instância — heartbeat e poll de tasks convergem nela |
|
|
97
|
-
| `tags` | `Record<string,string>` | — | tags aplicadas a todos os eventos |
|
|
98
|
-
| `sampleRate` | `number` | `1` | amostragem de erros (0..1) |
|
|
99
|
-
| `tracesSampleRate` | `number` | `1` | amostragem de transactions (0..1) |
|
|
100
|
-
| `maxBreadcrumbs` | `number` | `100` | tamanho do buffer de breadcrumbs |
|
|
101
|
-
| `captureUnhandled` | `boolean` | `true` | handlers globais de erro (`uncaughtException`/`unhandledRejection`) |
|
|
102
|
-
| `machineMetrics` | `boolean` | `true` | CPU/memória/disco no heartbeat, no poll e em `contexts.machine` dos eventos |
|
|
103
|
-
| `heartbeat` | `boolean \| HeartbeatOptions` | `false` | heartbeat periódico (`true` usa defaults) |
|
|
104
|
-
| `tasks` | `boolean \| TasksOptions` | `false` | habilita a execução sob demanda (`true` usa defaults) — veja [Tasks](#tasks-execução-sob-demanda) |
|
|
105
|
-
| `beforeSend` | `(event) => event \| null` | — | edita/descarta evento (retorne `null` para descartar) |
|
|
106
|
-
| `debug` | `boolean` | `false` | loga atividade do SDK |
|
|
107
|
-
| `flushIntervalMs` | `number` | `2000` | intervalo do flush automático do transport |
|
|
108
|
-
| `maxBatchSize` | `number` | `10` | itens por envelope antes do flush imediato |
|
|
109
|
-
|
|
110
|
-
### Captura de erros e mensagens
|
|
111
|
-
|
|
112
|
-
```js
|
|
113
|
-
try {
|
|
114
|
-
await processarNota(nota);
|
|
115
|
-
} catch (error) {
|
|
116
|
-
Hobots.captureException(error, {
|
|
117
|
-
type: 'error',
|
|
118
|
-
tags: { cliente: 'acme' },
|
|
119
|
-
fingerprint: ['sefaz-timeout'], // agrupa manualmente; sem isso o servidor agrupa pelo stack
|
|
120
|
-
});
|
|
121
|
-
throw error;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
Hobots.captureMessage('Lote processado com sucesso', 'success');
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
Ambas retornam o `event_id` (ou `undefined` se não inicializado). O hint aceita `type`, `tags`, `extra` e `fingerprint`. Erros **não tratados** são capturados e flushados automaticamente antes do processo sair — desligue com `captureUnhandled: false`. Os eventos aparecem agrupados em issues no módulo **Issues** do app.
|
|
128
|
-
|
|
129
|
-
`EventType = 'fatal' | 'error' | 'warning' | 'success'`. Não há `'info'`/`'debug'`: narração não abre issue — para isso existem o log da execução e as breadcrumbs. Use `'success'` para reportar itens concluídos com êxito — contam como eventos, mas **não** viram issue nem disparam alertas.
|
|
130
|
-
|
|
131
|
-
### Breadcrumbs
|
|
132
|
-
|
|
133
|
-
Trilha do que aconteceu antes do erro; enviada junto do próximo evento (buffer limitado por `maxBreadcrumbs`, mais antigos descartados).
|
|
25
|
+
## Em 30 segundos
|
|
134
26
|
|
|
135
|
-
```
|
|
136
|
-
|
|
27
|
+
```bash
|
|
28
|
+
export HOBOTS_CLIENT_SECRET=hb_<64 hex> # Configurações → Agentes
|
|
29
|
+
export HOBOTS_INSTANCE_ID=vm-cliente-01 # opcional — default: hostname da máquina
|
|
137
30
|
```
|
|
138
31
|
|
|
139
|
-
A lista **não** é limpa ao enviar um evento e é global. Conceito: [Breadcrumb](./docs/concepts.md#breadcrumb).
|
|
140
|
-
|
|
141
|
-
### Escopo: tags, usuário, contexto, extras
|
|
142
|
-
|
|
143
|
-
Dados anexados a **todos** os eventos seguintes:
|
|
144
|
-
|
|
145
32
|
```js
|
|
146
|
-
|
|
147
|
-
Hobots.setTags({ squad: 'automacoes', cliente: 'acme' });
|
|
148
|
-
Hobots.setUser({ id: 'op-42', email: 'op@acme.com' });
|
|
149
|
-
Hobots.setContext('vm', { cpu: 4, ram: '8GB' });
|
|
150
|
-
Hobots.setExtra('ultimoLote', 128);
|
|
151
|
-
```
|
|
33
|
+
import hobots from 'hobots';
|
|
152
34
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
35
|
+
// sob demanda: uma execução por solicitação do app (o task_slug casa com o formulário)
|
|
36
|
+
hobots.register('processar-nfe', async params => {
|
|
37
|
+
hobots.log.info(`lote ${params.lote}`);
|
|
38
|
+
hobots.progress.total(params.notas.length);
|
|
39
|
+
for (const nota of params.notas) {
|
|
40
|
+
await baixar(nota);
|
|
41
|
+
hobots.items.succeeded({ numero_nota: nota }, { id: nota });
|
|
42
|
+
hobots.progress.advance();
|
|
43
|
+
}
|
|
159
44
|
});
|
|
160
|
-
```
|
|
161
45
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
step.setData('notas', 42).finish();
|
|
170
|
-
|
|
171
|
-
const filho = step.startChild('db.query', 'INSERT INTO notas'); // etapas aninham
|
|
172
|
-
await gravar();
|
|
173
|
-
filho.finish();
|
|
174
|
-
|
|
175
|
-
tx.setStatus('ok'); // ou 'error'
|
|
176
|
-
tx.finish(); // só aqui a transaction é enviada (respeitando tracesSampleRate)
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
- `startChild(op?, description?)` existe em `Transaction` **e** `Step` (aninhamento arbitrário).
|
|
180
|
-
- Métodos encadeáveis: `setStatus`, `setData` (step), `setTag`/`setName` (transaction).
|
|
181
|
-
- `finish()` na transaction fecha as etapas abertas e envia o payload; nada é enviado se ela não foi amostrada. A amostragem é decidida no `startTransaction()`.
|
|
182
|
-
|
|
183
|
-
Dentro de um handler de task, `startTransaction()` **retorna a transaction da própria run** (`ctx.tx`) em vez de criar uma segunda — cada execução tem exatamente **uma** transaction. Conceitos: [Transaction](./docs/concepts.md#transaction) · [Step](./docs/concepts.md#step).
|
|
184
|
-
|
|
185
|
-
### Heartbeat (uptime)
|
|
186
|
-
|
|
187
|
-
Bate "estou vivo" periodicamente; o servidor acusa o robô **offline** quando os batimentos param.
|
|
188
|
-
|
|
189
|
-
```js
|
|
190
|
-
Hobots.init({
|
|
191
|
-
clientSecret,
|
|
192
|
-
heartbeat: { intervalMs: 30_000, name: 'Robô NFe — VM 01' },
|
|
46
|
+
// sempre ligado: o handler é o loop do robô; cada iteração reporta o que fez
|
|
47
|
+
hobots.start('monitor-pasta', async () => {
|
|
48
|
+
while (true) {
|
|
49
|
+
await hobots.sleep(60_000); // cancelável
|
|
50
|
+
for (const arquivo of await novosArquivos()) hobots.items.succeeded({ arquivo });
|
|
51
|
+
hobots.log.info('ciclo ok');
|
|
52
|
+
}
|
|
193
53
|
});
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
`heartbeat: true` usa os defaults. A identidade é o `instanceId` do `init()`; os campos são `intervalMs`, `name` (default: o `instanceId`) e `metadata` — valores em [Tempos e intervalos](./docs/api-reference.md#tempos-e-intervalos). O primeiro batimento sai **na hora** em que o `init()` roda, não depois do primeiro intervalo.
|
|
197
|
-
|
|
198
|
-
O heartbeat é **essencial** para agentes só de telemetria; com `tasks: true` ele é **opcional**, porque o poll de tasks já marca a presença do agente no painel. Conceito: [Heartbeat](./docs/concepts.md#heartbeat).
|
|
199
54
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
Ligado por padrão em Node (`machineMetrics: true`), o SDK coleta CPU do sistema, memória, disco do volume do bot e RSS/CPU do próprio processo — **sem dependência nova** e sem timer próprio: a leitura pega carona em cada heartbeat e em cada poll de tasks (série da instância em **Instâncias → recursos da máquina**) e vai como `contexts.machine` em cada evento (card **"máquina no momento do erro"** no detalhe da issue). Assim dá para responder se o ambiente tem recurso suficiente e se um erro coincidiu com CPU/memória no limite.
|
|
203
|
-
|
|
204
|
-
```js
|
|
205
|
-
Hobots.init({ clientSecret, instanceId: 'vm-01', heartbeat: true }); // coleta ligada
|
|
206
|
-
Hobots.init({ clientSecret, instanceId: 'vm-01', machineMetrics: false }); // desliga
|
|
55
|
+
process.on('SIGINT', () => void hobots.close(15_000).then(() => process.exit(0)));
|
|
207
56
|
```
|
|
208
57
|
|
|
209
|
-
O
|
|
210
|
-
|
|
211
|
-
### Flush e close
|
|
212
|
-
|
|
213
|
-
```js
|
|
214
|
-
await Hobots.flush(2000); // envia o buffer, mantém o SDK vivo
|
|
215
|
-
await Hobots.close(2000); // flush final + para o heartbeat + fecha o transport
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
Chame `close` antes do processo sair. Os timers da telemetria são `unref`'d — ela **não** segura o processo vivo sozinha.
|
|
58
|
+
O objeto **`hobots`** é global: `hobots.log`, `hobots.items`, `hobots.progress`, `hobots.transaction`… resolvem a execução **atual** de qualquer módulo do programa — sem passar contexto de mão em mão, mesmo com várias execuções em paralelo. Depois da primeira task ele também existe como `globalThis.hobots`.
|
|
219
59
|
|
|
220
60
|
---
|
|
221
61
|
|
|
222
|
-
##
|
|
62
|
+
## Credenciais e identidade
|
|
223
63
|
|
|
224
|
-
|
|
64
|
+
| Env | Opção | O que é |
|
|
65
|
+
| --- | --- | --- |
|
|
66
|
+
| `HOBOTS_CLIENT_SECRET` | `clientSecret` | O **Client Secret do agente** (`hb_<64 hex>`), copiado em **Configurações → Agentes**. Viaja só no header `Authorization: Bearer`, nunca na URL. Ausente ou inválido → `register()`/`start()` lança na hora, citando a env (sem ecoar o valor). |
|
|
67
|
+
| `HOBOTS_INSTANCE_ID` | `instanceId` | Identidade **estável** desta instância do robô (`vm-cliente-01`). Sem ela, o hostname. Heartbeat e tasks convergem nela: **um agente só** no painel. |
|
|
68
|
+
| `HOBOTS_API_URL` | — | Só em desenvolvimento: aponta o SDK para outro host (`http://localhost:3001`). Em produção é sempre `https://api2.hobots.app`. |
|
|
225
69
|
|
|
226
|
-
|
|
70
|
+
A opção passada em `register()`/`start()` tem precedência sobre a env — útil para copiar e colar o snippet do app.
|
|
227
71
|
|
|
228
|
-
|
|
229
|
-
Hobots.init({
|
|
230
|
-
clientSecret: 'hb_<64 hex>',
|
|
231
|
-
instanceId: 'vm-cliente-01',
|
|
232
|
-
tasks: true, // ou { pollIntervalMs, concurrency, ... }
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
Hobots.register('processar-nfe', async (params, ctx) => {
|
|
236
|
-
ctx.log.info(`processando lote ${params.lote}…`);
|
|
237
|
-
ctx.throwIfCanceled(); // cancelamento cooperativo
|
|
238
|
-
ctx.log.info('12 notas processadas');
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
Hobots.start(); // começa o polling e MANTÉM O PROCESSO VIVO
|
|
242
|
-
|
|
243
|
-
process.on('SIGINT', () => {
|
|
244
|
-
void Hobots.close(15_000).then(() => process.exit(0));
|
|
245
|
-
});
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
- `init({ tasks })` — cria o agente. Chamar `init()` de novo para o agente anterior e o substitui (o registry de handlers é preservado).
|
|
249
|
-
- `register(task, handler)` — `task` é o `task_slug` configurado no formulário do app (**Configurações → Formulários**). Registre **antes** de `start()`. Funciona até antes do `init()`; com as tasks desabilitadas, avisa no console.
|
|
250
|
-
- `start()` — começa o polling. Lança se o `init()` não configurou `tasks` ou se nenhuma task foi registrada.
|
|
251
|
-
- `stop(timeoutMs?)` — para **só o polling de tasks**: encerra o polling e aguarda as runs ativas (default 30s); a telemetria continua. Retorna `false` se estourou o timeout.
|
|
252
|
-
- `close(timeoutMs?)` — encerra **tudo**: agente (aguardando runs em voo) → heartbeat → flush final do transport.
|
|
253
|
-
|
|
254
|
-
| Opção (`TasksOptions`) | Tipo | Default | Descrição |
|
|
255
|
-
| --- | --- | --- | --- |
|
|
256
|
-
| `pollIntervalMs` | `number` | `2000` | intervalo entre polls sem trabalho (mínimo `1000`) |
|
|
257
|
-
| `concurrency` | `number` | `1` | runs simultâneas (clamp 1..5) |
|
|
258
|
-
| `taskTimeoutMs` | `number` | — | timeout client-side por run (não mata o handler; servidor é o backstop) |
|
|
259
|
-
| `logFlushIntervalMs` | `number` | `2000` | intervalo do envio incremental de logs |
|
|
260
|
-
| `cancelCheckIntervalMs` | `number` | `5000` | heartbeat de cancelamento por run (clamp 1000..15000) — também é o sinal de vida da run |
|
|
261
|
-
| `installExitHandlers` | `boolean` | `true` | handlers de SIGINT/SIGTERM/uncaughtException que reportam as runs ativas como `AgentTerminated` antes do processo morrer; nunca interferem quando o programa tem handler próprio |
|
|
262
|
-
|
|
263
|
-
O Client Secret, o `debug` e a identidade do agente (`instanceId`) vêm do `init()` — o heartbeat e o poll de tasks convergem em **um agente** no painel automaticamente.
|
|
264
|
-
|
|
265
|
-
### O handler e o `RunContext`
|
|
266
|
-
|
|
267
|
-
```ts
|
|
268
|
-
type TaskHandler<P = Record<string, unknown>> =
|
|
269
|
-
(params: P, ctx: RunContext) => void | Promise<void>;
|
|
270
|
-
|
|
271
|
-
interface RunContext {
|
|
272
|
-
runId: string;
|
|
273
|
-
task: string;
|
|
274
|
-
log: RunLogger; // ctx.log.debug/info/warn/error/success(message)
|
|
275
|
-
items: RunItems; // ctx.items.succeeded/failed/occurrence — a entrega do bot
|
|
276
|
-
progress: RunProgressReporter; // ctx.progress.total/advance/set — barra + tempo restante no app
|
|
277
|
-
tx: Transaction; // transaction desta run — correlacionada e finalizada pelo SDK
|
|
278
|
-
signal: AbortSignal;
|
|
279
|
-
throwIfCanceled(): void; // lança RunCanceled se o signal já disparou
|
|
280
|
-
sleep(ms: number): Promise<void>; // espera cancelável — rejeita com RunCanceled no abort
|
|
281
|
-
}
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
- **`params`** — os dados do formulário configurado no app (JSON validado). Tipável: `Hobots.register<{ lote: number }>(...)`.
|
|
285
|
-
- **`ctx.log.<level>(message)`** — níveis `debug`, `info`, `warn`, `error` e `success`; logs enviados **incrementalmente** ao app (PATCHes com throttle), visíveis na solicitação quase em tempo real; também impressos no terminal local, coloridos por nível.
|
|
286
|
-
- **`ctx.items`** — registra os **itens que a execução processou/gerou** (a entrega do bot: notas fiscais baixadas, boletos emitidos…), cada um com status `succeeded`, `failed` (falha técnica) ou `occurrence` (ocorrência de negócio, ex.: "nota nº 10 não encontrada") e um `payload` de atributos livres. Aparecem na aba **itens** da solicitação, com totalizadores; as colunas exibidas são configuradas por formulário no app. Guia: [Itens de execução](./docs/tasks.md#itens-de-execução-ctxitems).
|
|
287
|
-
- **`ctx.progress`** — **opcional**: declare quantos itens a run vai processar (`ctx.progress.total(n)`) e avance a cada item (`ctx.progress.advance()`); o app mostra barra, percentual e tempo restante estimado na solicitação. Independente de `ctx.items`. Guia: [Progresso](./docs/tasks.md#progresso-da-execução-ctxprogress).
|
|
288
|
-
- **`ctx.tx`** — a transaction de performance desta run, já correlacionada e finalizada pelo SDK. Use `ctx.tx.startChild(op, description)` para medir as etapas — elas viram o waterfall da execução no app. `startTransaction()` chamado dentro do handler retorna essa mesma transaction. Conceito: [ctx.tx](./docs/concepts.md#transaction-de-run-ctxtx).
|
|
289
|
-
- **`ctx.signal`** — `AbortSignal` que dispara quando a run é **cancelada no app**, em **timeout** ou no **shutdown**.
|
|
290
|
-
- **`ctx.throwIfCanceled()`** — o ponto de parada de uma linha; lança `RunCanceled` se o `signal` já disparou.
|
|
291
|
-
- **`ctx.sleep(ms)`** — espera que respeita o `signal` (rejeita com `RunCanceled` em vez de dormir até o fim).
|
|
292
|
-
- **Retorno** — **ignorado**: o SDK não captura nem envia o valor retornado pelo handler (resultados podem conter dados sensíveis e não são coletados).
|
|
293
|
-
- **Erro lançado** — a run é reportada como `failed` (mensagem cap 10k, stack 20k).
|
|
72
|
+
---
|
|
294
73
|
|
|
295
|
-
|
|
74
|
+
## Os dois modos
|
|
296
75
|
|
|
297
|
-
|
|
76
|
+
| | `register(task, handler, options?)` | `start(task, handler, options?)` |
|
|
77
|
+
| --- | --- | --- |
|
|
78
|
+
| Quem dispara | Uma **solicitação** no app (formulário com esse `task_slug`, ou agendamento) | O próprio robô, ao iniciar |
|
|
79
|
+
| `params` do handler | O JSON validado do formulário | `{}` |
|
|
80
|
+
| Quantas execuções | Uma por solicitação; `concurrency` em paralelo (1..5) | **Uma só** — o handler é o seu loop; quando ele retorna, a execução fecha |
|
|
81
|
+
| O processo | Fica vivo fazendo polling | Fica vivo pelo heartbeat, mesmo depois do handler retornar |
|
|
82
|
+
| Reexecutar pelo app | Sim | Não — só reiniciando o robô |
|
|
298
83
|
|
|
299
|
-
|
|
300
|
-
Hobots.register('baixar-notas', async (params, ctx) => {
|
|
301
|
-
for (const nota of await listarNotas(params)) {
|
|
302
|
-
try {
|
|
303
|
-
await baixar(nota);
|
|
304
|
-
ctx.items.succeeded({ numero_nota: nota.numero, cnpj_prestador: nota.cnpj }, { id: nota.numero });
|
|
305
|
-
} catch (erro) {
|
|
306
|
-
ctx.items.failed(String(erro), { numero_nota: nota.numero }, { id: nota.numero });
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
// ocorrência de negócio (não é falha do bot):
|
|
310
|
-
// ctx.items.occurrence('nota nº 10 não encontrada', { numero_nota: '10' }, { id: '10' })
|
|
311
|
-
});
|
|
312
|
-
```
|
|
84
|
+
Os dois podem coexistir no mesmo processo: um monitor sempre ligado + tasks sob demanda, um heartbeat, uma instância. Cada slug só pode ser registrado uma vez.
|
|
313
85
|
|
|
314
|
-
|
|
86
|
+
> **Modo start e o limite de logs:** o servidor guarda no máximo **1000 linhas de log por execução**. Num loop que roda por dias, logue o que importa (ciclos com novidade, avisos, erros) e registre o trabalho em `hobots.items` — itens não têm esse teto tão baixo (10.000 por execução).
|
|
315
87
|
|
|
316
|
-
|
|
88
|
+
---
|
|
317
89
|
|
|
318
|
-
|
|
90
|
+
## O objeto `hobots`
|
|
319
91
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
})
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
92
|
+
| Membro | O que faz |
|
|
93
|
+
| --- | --- |
|
|
94
|
+
| `hobots.log.debug/info/warn/error/success(msg)` | Log da execução, enviado incrementalmente ao app (e ecoado no terminal). **Nunca lança** — pode ir em `finally`. |
|
|
95
|
+
| `hobots.items.succeeded(payload?, { id }?)` · `.failed(msg, payload?, …)` · `.occurrence(msg, payload?, …)` · `.add({...})` | **A entrega do robô**: cada item processado, com status e atributos (viram colunas no app). Bufferizado, em lotes. |
|
|
96
|
+
| `hobots.progress.total(n)` · `.advance(k = 1)` · `.set(atual, total?)` · `.current` · `.snapshot` | Barra, percentual e tempo restante na solicitação. Sem `total()` nada é enviado. |
|
|
97
|
+
| `hobots.transaction.startChild(op, descrição)` | Uma **step** no waterfall de performance da execução. A transaction da execução já existe e é finalizada pelo SDK. |
|
|
98
|
+
| `hobots.attach(imagem, { caption?, level? })` | Pendura um **print** (PNG/JPEG/WEBP, até 5 MB, 20 por execução) na linha de log. Nunca lança nem bloqueia. |
|
|
99
|
+
| `hobots.captureException(erro, hint?)` | Abre uma **issue** (módulo Issues). `hint.attachment` anexa um print à linha de log correspondente. |
|
|
100
|
+
| `hobots.retry(fn, { retries = 3, delayMs = 1_000, shouldRetry? })` | async-retry ciente do cancelamento da execução. `delayMs` aceita função `(tentativa) => ms`. |
|
|
101
|
+
| `hobots.sleep(ms)` | Espera **cancelável**: rejeita com `RunCanceled` se a execução for cancelada. |
|
|
102
|
+
| `hobots.signal` · `hobots.throwIfCanceled()` | Cancelamento cooperativo manual, para controle fino. |
|
|
103
|
+
| `hobots.runId` · `hobots.task` · `hobots.params` | Identidade da execução atual. |
|
|
104
|
+
| `hobots.register(...)` · `hobots.start(...)` · `hobots.close(timeoutMs?)` | Ciclo de vida do processo. |
|
|
105
|
+
|
|
106
|
+
O handler também recebe o mesmo contexto como 2º argumento (`(params, ctx) => …`), para quem prefere explícito. Fora de qualquer execução (ex.: antes do boot), os membros de contexto caem em fallbacks seguros e avisam uma vez — `hobots.log` continua imprimindo no terminal. Evite `const { log } = hobots` no topo de um módulo: o valor capturado é o de fora de execução; chame `hobots.log.info(...)`.
|
|
332
107
|
|
|
333
|
-
|
|
108
|
+
---
|
|
334
109
|
|
|
335
|
-
|
|
110
|
+
## Cancelamento automático
|
|
336
111
|
|
|
337
|
-
|
|
338
|
-
Hobots.register('processar-nfe', async (params, ctx) => {
|
|
339
|
-
await entrar();
|
|
340
|
-
Hobots.captureMessage('portal após o login', 'info', {
|
|
341
|
-
attachment: await page.screenshot(),
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
try {
|
|
345
|
-
await emitir(params);
|
|
346
|
-
} catch (erro) {
|
|
347
|
-
Hobots.captureException(erro, {
|
|
348
|
-
attachment: { image: await page.screenshot(), caption: 'tela no momento do erro' },
|
|
349
|
-
});
|
|
350
|
-
throw erro;
|
|
351
|
-
}
|
|
352
|
-
});
|
|
353
|
-
```
|
|
112
|
+
Uma execução cancelada no app **não mata** o handler — o SDK dispara `hobots.signal` e o próximo ponto de parada lança **`RunCanceled`**:
|
|
354
113
|
|
|
355
|
-
|
|
114
|
+
- `hobots.items.*`, `hobots.progress.total/advance/set`, `hobots.transaction.startChild()`, `hobots.sleep()` e cada tentativa de `hobots.retry()`.
|
|
115
|
+
- **Nunca `hobots.log.*`** — um log dentro de um `finally` de limpeza não pode explodir.
|
|
356
116
|
|
|
357
|
-
|
|
358
|
-
| --- | --- |
|
|
359
|
-
| formatos | PNG, JPEG, WEBP |
|
|
360
|
-
| tamanho por imagem | 5 MB |
|
|
361
|
-
| anexos por execução | 20 |
|
|
362
|
-
| anexos por linha de log | 4 |
|
|
117
|
+
Num loop de itens, isso costuma bastar: registrar o item processado já é o ponto de parada. Para controle fino, `hobots.throwIfCanceled()` e `hobots.signal` continuam disponíveis. Quando o cancelamento veio do app, o `RunCanceled` é **suprimido** — a execução já está `cancelada` no servidor e nada é reportado por cima. Limpeza (fechar navegador, desfazer estado) vai em `try/finally`.
|
|
363
118
|
|
|
364
|
-
|
|
119
|
+
> **Atenção:** não use `if (hobots.signal.aborted) return;`. Retornar normalmente reporta a execução como **`succeeded`**. É preciso **lançar** — e é o que os pontos de parada fazem.
|
|
365
120
|
|
|
366
|
-
|
|
121
|
+
---
|
|
367
122
|
|
|
368
|
-
|
|
123
|
+
## Retry
|
|
369
124
|
|
|
370
|
-
|
|
125
|
+
Quando o handler lança, o SDK **tenta de novo na mesma execução**: aviso no log (`tentativa 1/2 falhou: … — nova tentativa em 5000ms`), espera cancelável, re-invocação com os mesmos `params`. Default: **1 tentativa extra** com 5 s. Itens e progresso já enviados permanecem — escreva handlers **idempotentes** (use o `id` de negócio nos itens: repetido, o servidor ignora).
|
|
371
126
|
|
|
372
127
|
```js
|
|
373
|
-
|
|
374
|
-
for (const nota of params.notas) {
|
|
375
|
-
ctx.throwIfCanceled(); // lança RunCanceled se cancelada — o handler para aqui
|
|
376
|
-
await processar(nota);
|
|
377
|
-
}
|
|
378
|
-
});
|
|
128
|
+
hobots.register('processar-nfe', handler, { retries: 3, retryDelayMs: attempt => attempt * 10_000 });
|
|
379
129
|
```
|
|
380
130
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
> **Atenção:** não use `if (ctx.signal.aborted) return;`. Retornar normalmente faz o SDK reportar a run como **`succeeded`** — uma run cancelada apareceria como sucesso no app. É preciso **lançar**, e é o que o `throwIfCanceled()` faz.
|
|
131
|
+
Nunca há nova tentativa em **cancelamento**, **timeout** (`TaskTimeout` é terminal) ou execução já cancelada no app. Para uma chamada instável dentro do handler, use `hobots.retry()`.
|
|
384
132
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
`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**: re-pesquisa imediatamente enquanto vem trabalho; quando não vem, espera `pollIntervalMs`.
|
|
388
|
-
|
|
389
|
-
### Timeout por run
|
|
390
|
-
|
|
391
|
-
`taskTimeoutMs` é **client-side**. No estouro o SDK **não mata** o handler — dispara `ctx.signal`, libera o slot e reporta a run como falha (`TaskTimeout`). Respeite o `signal` para parar de verdade. Sem ele, o backstop é o servidor.
|
|
392
|
-
|
|
393
|
-
### Idempotência (at-least-once até `running`)
|
|
133
|
+
---
|
|
394
134
|
|
|
395
|
-
|
|
135
|
+
## Opções
|
|
396
136
|
|
|
397
|
-
|
|
137
|
+
```ts
|
|
138
|
+
hobots.register(task, handler, options?: RegisterOptions);
|
|
139
|
+
hobots.start(task, handler, options?: StartOptions);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
| Opção | Default | Descrição |
|
|
143
|
+
| --- | --- | --- |
|
|
144
|
+
| `clientSecret` | env `HOBOTS_CLIENT_SECRET` | Client Secret do agente |
|
|
145
|
+
| `instanceId` | env `HOBOTS_INSTANCE_ID` → hostname | Identidade da instância |
|
|
146
|
+
| `environment` | `'production'` | `development` \| `testing` \| `staging` \| `production` — decide quem é alertado |
|
|
147
|
+
| `release` | — | Versão do programa, ex. `robo-nfe@1.4.2` |
|
|
148
|
+
| `tags` | — | Tags de todos os eventos |
|
|
149
|
+
| `debug` | `false` | Loga a atividade do SDK |
|
|
150
|
+
| `machineMetrics` | `true` | CPU/memória/disco no heartbeat e em cada evento |
|
|
151
|
+
| `captureUnhandled` | `true` | `uncaughtException`/`unhandledRejection` viram issues |
|
|
152
|
+
| `retries` | `1` | Tentativas extras do handler (0 desliga) |
|
|
153
|
+
| `retryDelayMs` | `5_000` | ms fixo ou `(tentativa) => ms` |
|
|
154
|
+
| `timeoutMs` | — (infinito) | Timeout por tentativa; terminal (`TaskTimeout`). O handler não é morto: o signal dispara e o slot é liberado |
|
|
155
|
+
| `concurrency` (register) | `1` | Execuções simultâneas desta task (1..5) |
|
|
156
|
+
| `pollIntervalMs` (register) | `2_000` | Intervalo entre polls sem trabalho (mín. 1_000) |
|
|
157
|
+
| avançadas: `installExitHandlers`, `logFlushIntervalMs`, `cancelCheckIntervalMs`, `cancelGraceMs` | `true`, `2_000`, `5_000`, `10_000` | Ver [Referência de API](./docs/api-reference.md) |
|
|
158
|
+
|
|
159
|
+
As opções de **processo** (`clientSecret`, `instanceId`, `environment`, `release`, `tags`, `debug`, `machineMetrics`, `captureUnhandled`, `installExitHandlers`, `pollIntervalMs`) valem as da **primeira** chamada; chamadas seguintes com valor diferente avisam e são ignoradas.
|
|
398
160
|
|
|
399
161
|
---
|
|
400
162
|
|
|
401
|
-
## Encerramento
|
|
163
|
+
## Encerramento
|
|
402
164
|
|
|
403
|
-
`close(timeoutMs
|
|
165
|
+
O heartbeat mantém o processo vivo por design. `hobots.close(timeoutMs = 30_000)` para o polling, aguarda as execuções em voo, para o heartbeat (libera o processo) e faz o flush final:
|
|
404
166
|
|
|
405
167
|
```js
|
|
406
|
-
// só telemetria: não segura o processo, mas faça o flush final para não perder eventos
|
|
407
|
-
await Hobots.close(2_000);
|
|
408
|
-
|
|
409
|
-
// com tasks: segura o processo por design — aguarde a run atual
|
|
410
168
|
process.on('SIGINT', () => {
|
|
411
|
-
void
|
|
169
|
+
void hobots.close(15_000).then(() => process.exit(0));
|
|
412
170
|
});
|
|
413
171
|
```
|
|
414
172
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
---
|
|
418
|
-
|
|
419
|
-
## Entry points / exports
|
|
420
|
-
|
|
421
|
-
| Import | Conteúdo |
|
|
422
|
-
| --- | --- |
|
|
423
|
-
| `hobots` | Tudo: `init`, telemetria (captura, transactions, heartbeat), tasks (`register`/`start`/`stop`), `close`, classes e tipos. |
|
|
424
|
-
| `hobots/protocol` | Tipos do wire format (telemetria e tasks). |
|
|
173
|
+
Sem handler próprio de sinal, um `SIGINT`/`SIGTERM` (ou erro fatal) com execuções ativas reporta cada uma como `failed`/`AgentTerminated` antes do processo sair — o app mostra a falha na hora.
|
|
425
174
|
|
|
426
|
-
|
|
175
|
+
> **Dados sensíveis:** logs, itens, mensagens de erro e prints são enviados ao servidor e ficam visíveis no app. O SDK redige padrões óbvios de segredo, mas não borra prints nem filtra dados pessoais. Capture só o necessário.
|
|
427
176
|
|
|
428
177
|
---
|
|
429
178
|
|
|
430
|
-
## Documentação
|
|
179
|
+
## Documentação
|
|
431
180
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
- [
|
|
437
|
-
- [
|
|
438
|
-
- [Telemetria](./docs/telemetry.md) — erros, breadcrumbs, escopo, transactions/etapas, heartbeat.
|
|
439
|
-
- [Tasks](./docs/tasks.md) — handlers, `RunContext`, cancelamento, concorrência, idempotência.
|
|
440
|
-
- [Referência de API](./docs/api-reference.md) — todas as funções e tabelas de opções com defaults.
|
|
441
|
-
- [Protocolo & transporte](./docs/protocol.md) — o Client Secret, endpoints, envelope, retries.
|
|
442
|
-
|
|
443
|
-
---
|
|
181
|
+
- [Getting started](./docs/getting-started.md) — instalação, envs, primeira task nos dois modos.
|
|
182
|
+
- [Tasks](./docs/tasks.md) — handler, `hobots.*`, itens, progresso, anexos, cancelamento, retry, concorrência, idempotência.
|
|
183
|
+
- [Conceitos](./docs/concepts.md) — o vocabulário: task, execução, evento, transaction, step, heartbeat, instância, claim…
|
|
184
|
+
- [Referência de API](./docs/api-reference.md) — assinaturas, tabelas de opções e tempos.
|
|
185
|
+
- [Protocolo & transporte](./docs/protocol.md) — endpoints, envelope, retries, claim.
|
|
186
|
+
- Exemplos runnable em [`packages/hobots-sdk-demos`](../hobots-sdk-demos).
|
|
444
187
|
|
|
445
188
|
## Licença
|
|
446
189
|
|