hobots 4.1.0 → 4.5.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 CHANGED
@@ -27,11 +27,13 @@ CommonJS: `const { hobots } = require('hobots')`.
27
27
  ## Em 30 segundos
28
28
 
29
29
  ```bash
30
- export HOBOTS_CLIENT_SECRET=hb_<64 hex> # Configurações → Agentes
30
+ export HOBOTS_CLIENT_SECRET=hb_<64 hex> # Central: agente → Configurações → agente → aba credenciais
31
31
  export HOBOTS_INSTANCE_ID=vm-cliente-01 # opcional — default: hostname da máquina
32
32
  export HOBOTS_ENVIRONMENT=staging # opcional — default: production
33
33
  ```
34
34
 
35
+ Ou num arquivo `.env` na pasta de onde o bot é executado — o SDK o carrega sozinho (sem `dotenv`), veja [Credenciais e identidade](#credenciais-e-identidade).
36
+
35
37
  ```js
36
38
  import hobots from 'hobots';
37
39
 
@@ -55,8 +57,14 @@ Um robô que vive rodando usa `start` no lugar de `register` — o handler é o
55
57
  hobots.start(async () => {
56
58
  while (true) {
57
59
  await hobots.sleep(60_000); // cancelável
58
- for (const arquivo of await novosArquivos()) hobots.items.succeeded({ arquivo });
60
+ for (const arquivo of await novosArquivos()) {
61
+ const step = hobots.transaction.start('arquivo', arquivo);
62
+ await processar(arquivo);
63
+ step.succeeded();
64
+ hobots.items.succeeded({ arquivo });
65
+ }
59
66
  hobots.log.info('ciclo ok');
67
+ await hobots.flush(); // envia as etapas, itens e logs deste ciclo agora
60
68
  }
61
69
  });
62
70
  ```
@@ -69,13 +77,16 @@ O objeto **`hobots`** é global: `hobots.log`, `hobots.items`, `hobots.progress`
69
77
 
70
78
  | Env | Opção | O que é |
71
79
  | --- | --- | --- |
72
- | `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). |
80
+ | `HOBOTS_CLIENT_SECRET` | `clientSecret` | O **Client Secret do agente** (`hb_<64 hex>`), copiado na Central em **agente → Configurações → agente → aba credenciais**. 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). |
73
81
  | `HOBOTS_INSTANCE_ID` | `instanceId` | Identidade **estável** desta instância do robô (`vm-cliente-01`). Sem ela, o hostname. Heartbeat e execuções convergem nela: **um agente só** no painel. |
82
+ | `HOBOTS_RUN_ID` | `runId` | Definida por quem lança o processo (o Studio): o `register()` não faz poll — inicia essa run, executa o handler uma vez e sai. O código do bot não muda. |
74
83
  | `HOBOTS_ENVIRONMENT` | `environment` | Ambiente lógico do robô (`development` \| `testing` \| `staging` \| `production`). Sem ela, `production`. |
75
84
  | `HOBOTS_API_URL` | — | Só em desenvolvimento: aponta o SDK para outro host (`http://localhost:3001`). Em produção é sempre `https://api2.hobots.app`. |
76
85
 
77
86
  A opção passada em `register()`/`start()` tem precedência sobre a env — útil para copiar e colar o snippet do app.
78
87
 
88
+ **`.env` automático.** Ao ser importado, o SDK carrega o arquivo `.env` da pasta de trabalho (`process.cwd()`) em `process.env` — o bot não precisa de `dotenv` nem de `--env-file`, e o próprio código do bot também enxerga as variáveis (`process.env.SENHA_PORTAL`), inclusive no topo do módulo. A env já definida no ambiente **vence** o arquivo. Sem `.env`, nada muda. Usa o parser nativo do Node (`process.loadEnvFile`, Node ≥ 20.12); em Node mais antigo o arquivo é ignorado.
89
+
79
90
  ---
80
91
 
81
92
  ## Os dois modos
@@ -104,12 +115,13 @@ A opção passada em `register()`/`start()` tem precedência sobre a env — út
104
115
  | `hobots.items.each(lista, async (el, item) => …, { id, payload }?)` | **Loop com o contrato garantido**: cada elemento vira step + item; exceção no handler = item `failed` com a mensagem e o loop **segue** até o fim; progresso automático. Só cancelamento/timeout interrompem. |
105
116
  | `hobots.progress.total(n)` · `.advance(k = 1)` · `.set(atual, total?)` · `.current` · `.snapshot` | Barra, percentual e tempo restante na execução. Sem `total()` nada é enviado. |
106
117
  | `hobots.flush(timeoutMs?)` | **Entrega já** o que a execução fez: as steps raiz finalizadas viram transactions parciais (waterfall na Central sem esperar a run terminar) e itens, logs e progresso pendentes saem sem esperar o throttle. O bot decide quando — num agente `start()`, ao fim de cada rodada que processou itens. Aguarda com teto (15 s) e nunca lança. |
107
- | `hobots.transaction.startChild(op, descrição)` | Uma **step** no waterfall de performance da execução. A transaction da execução (`run`) já existe e é finalizada pelo SDK. `step.setStatus(status, msg?, { image })` / `setError(err, msg?, { image })` penduram um print na step. |
118
+ | `hobots.transaction.start(op, descrição)` | Uma **step** no waterfall de performance da execução. A transaction da execução (`run`) já existe e é finalizada pelo SDK. `step.setStatus(status, msg?, { image })` / `failed(err, msg?, { image })` penduram um print na step. |
108
119
  | `hobots.image(bytes \| caminho, { caption?, contentType? })` | Embrulha um **print** (PNG/JPEG/WEBP, até 5 MB, 20 por execução) para ir junto de um log, item ou step. Puro: o upload roda em segundo plano quando o evento é registrado; nunca lança nem bloqueia. |
109
120
  | `hobots.captureException(erro, hint?)` | Abre uma **issue** (módulo Issues). `hint.attachment` anexa um print à linha de log correspondente. |
110
121
  | `hobots.retry(fn, { retries = 3, delayMs = 1_000, shouldRetry? })` | async-retry ciente do cancelamento da execução. `delayMs` aceita função `(tentativa) => ms`. |
111
122
  | `hobots.sleep(ms)` | Espera **cancelável**: rejeita com `RunCanceled` se a execução for cancelada. |
112
123
  | `hobots.signal` · `hobots.throwIfCanceled()` | Cancelamento cooperativo manual, para controle fino. |
124
+ | `hobots.files.list()` · `.get(campo)` · `.download(refOuCampo, { dest }?)` · `.downloadAll(campo)` · `.read(refOuCampo)` | **Arquivos dos campos de upload** do formulário: baixa para o disco (ou para a memória) e consome na origem — ver [Arquivos do formulário](#arquivos-do-formulário). `isFileRef(valor)` reconhece um arquivo nos params. |
113
125
  | `hobots.runId` · `hobots.params` | Identidade da execução atual. |
114
126
  | `hobots.register(...)` · `hobots.start(...)` · `hobots.close(timeoutMs?)` | Ciclo de vida do processo. |
115
127
 
@@ -117,11 +129,40 @@ O handler também recebe o mesmo contexto como 2º argumento (`(params, ctx) =>
117
129
 
118
130
  ---
119
131
 
132
+ ## Arquivos do formulário
133
+
134
+ Um campo de **upload** no formulário do agente chega nos `params` como um `FileRef` — `{ id, name, size, contentType, storage }` — ou uma lista deles (campo "vários arquivos"). O bot não precisa saber de onde o arquivo veio: `hobots.files.download` traz para o disco desta máquina e devolve o caminho.
135
+
136
+ ```js
137
+ import { hobots } from 'hobots';
138
+
139
+ hobots.register(async () => {
140
+ // campo único: o caminho do arquivo baixado (pasta temporária da execução)
141
+ const planilha = await hobots.files.download('planilha');
142
+ await importarPlanilha(planilha);
143
+
144
+ // campo "vários arquivos": um caminho por arquivo, na ordem, na pasta escolhida
145
+ const notas = await hobots.files.downloadAll('notas', { dest: './entrada' });
146
+ for (const nota of notas) await lancarNota(nota);
147
+
148
+ // ou direto em memória (Buffer) — a cópia temporária é apagada
149
+ const contrato = await hobots.files.read('contrato');
150
+ });
151
+ ```
152
+
153
+ - **Cada arquivo é baixado uma vez.** O download consome a origem: enviado pela Central (`storage: 's3'`), o SDK pede uma URL temporária, baixa em streaming, confere o tamanho e **só então** confirma — o servidor apaga o arquivo na hora; uma segunda tentativa recebe `arquivo "x" já baixado ou expirado` (`TasksApiError` com `status` 410). Escolhido no Studio (`storage: 'local'`), o arquivo nunca sai do computador: o SDK copia de `$HOBOTS_LOCAL_FILES_DIR/<id>/` e apaga a pasta de origem — num bot de outro computador (sem a env ou sem a pasta), o erro é `arquivo "x" enviado pelo Studio de outro computador ou já consumido`.
154
+ - **Falha não perde o arquivo:** rede caiu, tamanho divergente, execução cancelada — nada é confirmado e o `download` pode ser tentado de novo (ex.: dentro de `hobots.retry`).
155
+ - **Onde o arquivo vai parar:** sem `dest`, numa pasta temporária **da execução, apagada quando ela termina** — guarde o que precisar além disso com `dest`. `dest` pasta (existente ou terminada em `/`) recebe o nome original; outro caminho vira o nome do arquivo final.
156
+ - `hobots.files.list()` lista todos os arquivos dos params; `hobots.files.get('campo')` devolve o `FileRef` (ou a lista); `download` aceita o nome do campo ou o próprio `FileRef` (um item da lista, por exemplo). Para campo com vários arquivos use `downloadAll` (o `download('campo')` lança, apontando para ele).
157
+ - Respeita o cancelamento: uma execução cancelada no meio do download lança `RunCanceled` e não consome o arquivo.
158
+
159
+ ---
160
+
120
161
  ## Cancelamento automático
121
162
 
122
163
  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`**:
123
164
 
124
- - `hobots.items.*`, `hobots.progress.total/advance/set`, `hobots.transaction.startChild()`, `hobots.sleep()` e cada tentativa de `hobots.retry()`.
165
+ - `hobots.items.*`, `hobots.progress.total/advance/set`, `hobots.transaction.start()`, `hobots.sleep()` e cada tentativa de `hobots.retry()`.
125
166
  - **Nunca `hobots.log.*`** — um log dentro de um `finally` de limpeza não pode explodir.
126
167
 
127
168
  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`.
@@ -165,6 +206,7 @@ hobots.start(handler, options?: StartOptions);
165
206
  | `timeoutMs` | — (infinito) | Timeout por tentativa; terminal (`TaskTimeout`). O handler não é morto: o signal dispara e o slot é liberado |
166
207
  | `concurrency` (register) | `1` | Execuções simultâneas do agente (1..5) |
167
208
  | `pollIntervalMs` (register) | `2_000` | Intervalo entre polls sem trabalho (mín. 1_000) |
209
+ | `runId` (register) | env `HOBOTS_RUN_ID` | Avançado: executa só esta run (já reivindicada, ex.: pelo Studio) sem poll e encerra o SDK |
168
210
  | avançadas: `installExitHandlers`, `logFlushIntervalMs`, `cancelCheckIntervalMs`, `cancelGraceMs` | `true`, `2_000`, `5_000`, `10_000` | Ver [Referência de API](./docs/api-reference.md) |
169
211
 
170
212
  Como só há uma chamada por processo, todas as opções são as **do agente** — não existe "opção de processo" separada da "opção da task".
@@ -189,6 +231,8 @@ Sem handler próprio de sinal, um `SIGINT`/`SIGTERM` (ou erro fatal) com execuç
189
231
 
190
232
  ## Changelog resumido
191
233
 
234
+ - **4.3.0** — `register()` com env `HOBOTS_RUN_ID` (ou opção `runId`) executa só essa run: `POST /v1/runs/{id}/start` no lugar do poll, handler uma vez e `close()`; tipos `RunStartRequest`/`RunStartResponse` em `hobots/protocol`; itens recusados por armazenamento cheio (`402 storage_limit`) viram um aviso só por episódio, sem parar os envios.
235
+ - **4.2.0** — **arquivos do formulário**: `hobots.files` (`list`, `get`, `download`, `downloadAll`, `read`) e `isFileRef`; tipos `FileRef`, `FileStorage`, `FileDownloadResponse` em `hobots/protocol`.
192
236
  - **4.0.0** — **o agente é a própria tarefa** (breaking): `register(handler, options?)`/`start(handler, options?)` **sem slug**; **uma chamada por processo** (a segunda lança); `hobots.task`/`ctx.task` removidos; tipos `TaskHandler`→`RunHandler`, `TaskOptions`→`RunOptions`, `TaskItemsOptions`→`ItemsOptions`; wire: `InstanceInfo.mode` (`on_demand` \| `always_on`) e `InstanceInfo.items` no lugar de `tasks`/`task_items`, endpoints em `/v1/runs/*`; eco no terminal `[hobots] INFO …`. Guia de migração no [CHANGELOG](./CHANGELOG.md).
193
237
  - **3.4.0** — `hobots.attach()` removido; o print vai junto do evento: `hobots.image(...)` no último argumento de `log.*`, `{ image }` em itens e steps.
194
238
  - **3.1.0** — `hobots.log.*` aceita vários argumentos (como `console.log`); `retries` inválido (`NaN`) cai no default em vez de tentar para sempre; 429 de itens e 429/501/503 de anexo são definitivos (sem esperar a ladder — `quota`/`unsupported` passam a funcionar de fato); timeout **por requisição** nos anexos; truncamento nos limites do servidor (tag 512, fingerprint 10×255, nome de transaction 500, 200 steps, `exception.value` 8192, `filename` 1024); PNG acima de 20 000 px não declara dimensões; PATCH terminal nunca passa de 100 logs.