hobots 3.4.0 → 4.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.
@@ -12,8 +12,8 @@ Sem dependências de runtime. Requer **Node 18+** (usa o `fetch` global).
12
12
 
13
13
  | Conceito | Em uma frase |
14
14
  | --- | --- |
15
- | [Task](./concepts.md#task) | O que o robô sabe fazer, identificado por um slug — registrado com `register` (sob demanda) ou `start` (sempre ligado) |
16
- | [Execução (run)](./concepts.md#execução-run) | Uma rodada de uma task, com logs, itens, progresso e resultado — uma solicitação no app |
15
+ | [Agente](./concepts.md#agente) | O seu robô — um processo, uma coisa que ele faz, iniciado com `register` (sob demanda) ou `start` (sempre ligado) |
16
+ | [Execução (run)](./concepts.md#execução-run) | Uma rodada do agente, com logs, itens, progresso e resultado — um trabalho no app |
17
17
  | [Item](./concepts.md#item) | Uma unidade de trabalho entregue pela execução (nota baixada, boleto emitido) |
18
18
  | [Heartbeat](./concepts.md#heartbeat) | Batimento "estou vivo" — sempre ligado; o servidor acusa offline quando para |
19
19
  | [instanceId](./concepts.md#instanceid) | A identidade da instância do robô no painel |
@@ -21,24 +21,24 @@ Sem dependências de runtime. Requer **Node 18+** (usa o `fetch` global).
21
21
 
22
22
  ## Credenciais
23
23
 
24
- Cada agente tem **um Client Secret** (`hb_<64 hex>`), copiado em **Configurações → Agentes**. O SDK lê do ambiente, como as libs da AWS:
24
+ Cada agente tem **um Client Secret** (`hb_<64 hex>`), copiado em **Configurações → Agentes**. O SDK lê do ambiente:
25
25
 
26
26
  ```bash
27
27
  export HOBOTS_CLIENT_SECRET=hb_<64 hex>
28
28
  export HOBOTS_INSTANCE_ID=vm-cliente-01 # opcional — sem ela, o hostname
29
29
  ```
30
30
 
31
- Também dá para passar `clientSecret`/`instanceId` nas opções da task (o snippet do app já vem assim). Em desenvolvimento, `HOBOTS_API_URL=http://localhost:3001` aponta para outra API; em produção é sempre `https://api2.hobots.app`.
31
+ Também dá para passar `clientSecret`/`instanceId` nas opções de `register()`/`start()` (o snippet do app já vem assim). Em desenvolvimento, `HOBOTS_API_URL=http://localhost:3001` aponta para outra API; em produção é sempre `https://api2.hobots.app`.
32
32
 
33
- ## Primeira task sob demanda (`register`)
33
+ ## Primeiro agente sob demanda (`register`)
34
34
 
35
- 1. No app, crie um formulário em **Configurações → Formulários** com o `task_slug` `processar-nfe`.
35
+ 1. No app, abra o agente em **Configurações → Agentes** e monte o **formulário do agente** (os campos que cada solicitação vai carregar).
36
36
  2. No robô:
37
37
 
38
38
  ```js
39
39
  import hobots from 'hobots';
40
40
 
41
- hobots.register('processar-nfe', async params => {
41
+ hobots.register(async params => {
42
42
  hobots.log.info(`lote ${params.lote}`);
43
43
  for (const nota of params.notas) {
44
44
  await baixar(nota);
@@ -50,14 +50,14 @@ hobots.register('processar-nfe', async params => {
50
50
  process.on('SIGINT', () => void hobots.close(15_000).then(() => process.exit(0)));
51
51
  ```
52
52
 
53
- 3. Rode o robô: ele aparece online em **Instâncias** e fica aguardando. Crie uma solicitação pelo formulário — a execução aparece em **Solicitações** com logs em tempo real.
53
+ 3. Rode o robô: ele aparece online em **Instâncias** (modo "sob demanda") e fica aguardando. Crie uma solicitação pelo formulário — a execução aparece em **Trabalhos** com logs em tempo real.
54
54
 
55
- ## Primeira task sempre ligada (`start`)
55
+ ## Primeiro agente sempre ligado (`start`)
56
56
 
57
57
  ```js
58
58
  import hobots from 'hobots';
59
59
 
60
- hobots.start('monitor-pasta', async () => {
60
+ hobots.start(async () => {
61
61
  hobots.log.info('monitor iniciado');
62
62
  while (true) {
63
63
  await hobots.sleep(60_000); // cancelável — para se a execução for cancelada no app
@@ -66,10 +66,12 @@ hobots.start('monitor-pasta', async () => {
66
66
  });
67
67
  ```
68
68
 
69
- O robô abre a própria execução ao iniciar e ela aparece em **Solicitações** com origem "agente". Quando o handler retorna (ou é cancelado), a execução fecha; o processo continua vivo pelo heartbeat até `hobots.close()`.
69
+ O robô abre a própria execução ao iniciar e ela aparece em **Trabalhos** com origem "agente". Quando o handler retorna (ou é cancelado), a execução fecha; o processo continua vivo pelo heartbeat até `hobots.close()`.
70
+
71
+ > **Uma chamada por processo.** `register()` ou `start()`, nunca os dois — a segunda chamada lança. Um robô que faz duas coisas são dois agentes (dois processos, dois Client Secrets).
70
72
 
71
73
  ## Encerramento
72
74
 
73
75
  `hobots.close(timeoutMs?)` para o polling, espera as execuções em voo, para o heartbeat e faz o flush final. Sem handler próprio de `SIGINT`/`SIGTERM`, o SDK reporta as execuções ativas como `AgentTerminated` antes do processo morrer.
74
76
 
75
- Próximos passos: [Tasks](./tasks.md) para itens, progresso, anexos, cancelamento e retry; [Referência de API](./api-reference.md) para todas as opções.
77
+ Próximos passos: [Execuções](./runs.md) para itens, progresso, anexos, cancelamento e retry; [Referência de API](./api-reference.md) para todas as opções.
package/docs/protocol.md CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
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
4
 
5
- Os arquivos `src/protocol.ts` e `src/tasks/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. Todos os tipos são exportados por `hobots/protocol` (e reexportados pelo import raiz `hobots`).
5
+ Os arquivos `src/protocol.ts` e `src/tasks/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. Todos os tipos são exportados por `hobots/protocol` (inclusive `InstanceMode` e `ItemsDeclaration`) (e reexportados pelo import raiz `hobots`).
6
6
 
7
7
  ## O Client Secret e os endpoints derivados
8
8
 
9
- Cada agente tem **um único Client Secret**, o mesmo para as duas capabilities:
9
+ Cada agente tem **um único Client Secret**, o mesmo para as duas capabilities. O agente é a própria tarefa: não há slug de task no wire — o que o agente faz é identificado pelo secret; a instância só declara **como** trabalha (`mode`).
10
10
 
11
11
  ```
12
12
  hb_<64 hex>
@@ -15,10 +15,10 @@ hb_<64 hex>
15
15
  | Canal | Endpoint | Autenticação |
16
16
  | --- | --- | --- |
17
17
  | Telemetria (ingestão) | `POST <baseUrl>/v1/ingest` | `Authorization: Bearer <clientSecret>` |
18
- | Tasks (execução) | `<baseUrl>/v1/tasks/*` | `Authorization: Bearer <clientSecret>` |
18
+ | Execuções (poll, runs, itens, anexos) | `<baseUrl>/v1/runs/*` | `Authorization: Bearer <clientSecret>` |
19
19
  | Anexos (binário) | URL presignada do S3 (PUT) | assinatura na própria URL — **sem** header `Authorization` |
20
20
 
21
- A credencial vem da opção `clientSecret` ou da env `HOBOTS_CLIENT_SECRET` (resolvida em `src/config.ts` no primeiro `register()`/`start()`, com a URL base `https://api2.hobots.app`). Em desenvolvimento a env `HOBOTS_API_URL` sobrescreve a URL base — apenas `http(s)`, e `http` para host fora de loopback gera aviso porque o secret trafega sem TLS. A credencial é **secreta** (formato `hb_` + 64 hex — outro formato é rejeitado na primeira task, sem ecoar o valor) e viaja **só no header** `Authorization: Bearer` — nunca na URL da requisição.
21
+ A credencial vem da opção `clientSecret` ou da env `HOBOTS_CLIENT_SECRET` (resolvida em `src/config.ts` no primeiro `register()`/`start()`, com a URL base `https://api2.hobots.app`). Em desenvolvimento a env `HOBOTS_API_URL` sobrescreve a URL base — apenas `http(s)`, e `http` para host fora de loopback gera aviso porque o secret trafega sem TLS. A credencial é **secreta** (formato `hb_` + 64 hex — outro formato é rejeitado no `register()`/`start()`, sem ecoar o valor) e viaja **só no header** `Authorization: Bearer` — nunca na URL da requisição.
22
22
 
23
23
  ## Telemetria — ingestão
24
24
 
@@ -47,7 +47,7 @@ Todo `transaction` carrega `run_id`: é assim que a [transaction da execução](
47
47
 
48
48
  ### MachineSnapshot
49
49
 
50
- Recursos da máquina onde a instância roda. Vai em `HeartbeatPayload.machine`, em `instance.machine` do `POST /v1/tasks/poll` e em `contexts.machine` dos eventos. Bytes em bytes, percentuais 0..100; campo ausente = não mensurável nesta plataforma. Teto: **4 KiB** serializado (heartbeat/poll → 422; `contexts.machine` idem). Chaves desconhecidas são descartadas pelo servidor.
50
+ Recursos da máquina onde a instância roda. Vai em `HeartbeatPayload.machine`, em `instance.machine` do `POST /v1/runs/poll` e em `contexts.machine` dos eventos. Bytes em bytes, percentuais 0..100; campo ausente = não mensurável nesta plataforma. Teto: **4 KiB** serializado (heartbeat/poll → 422; `contexts.machine` idem). Chaves desconhecidas são descartadas pelo servidor.
51
51
 
52
52
  | Campo | Tipo | Descrição |
53
53
  | --- | --- | --- |
@@ -66,11 +66,11 @@ Recursos da máquina onde a instância roda. Vai em `HeartbeatPayload.machine`,
66
66
 
67
67
  O servidor guarda o último snapshot em `instances.machine` e um ponto por heartbeat/poll (máx. 1 a cada 30s por instância) em `instance_metrics`, expurgado em 14 dias.
68
68
 
69
- ## Tasks — polling, criação e reporte
69
+ ## Execuções — polling, criação e reporte
70
70
 
71
- Endpoints sob `<baseUrl>/v1/tasks`, todos com `Authorization: Bearer <clientSecret>` (a credencial única do agente):
71
+ Endpoints sob `<baseUrl>/v1/runs`, todos com `Authorization: Bearer <clientSecret>` (a credencial única do agente):
72
72
 
73
- ### `POST /v1/tasks/poll`
73
+ ### `POST /v1/runs/poll`
74
74
 
75
75
  Corpo `PollRequest`:
76
76
 
@@ -82,32 +82,31 @@ Corpo `PollRequest`:
82
82
  "version": "3.0.0",
83
83
  "host": "vm-01",
84
84
  "environment": "production", // ambiente da instância: development | testing | staging | production
85
- "tasks": ["processar-nfe", "monitor-pasta"], // TODAS as tasks (register + start) — o painel mostra o que o agente sabe fazer
85
+ "mode": "on_demand", // como a instância trabalha: on_demand (register) | always_on (start)
86
86
  "poll_interval_ms": 2000,
87
- // colunas de itens declaradas no código (opção `items`) — só as tasks que declararam;
87
+ // colunas de itens declaradas no código (opção `items`) — uma declaração, do agente;
88
88
  // ausente = nada declarado (o servidor nunca apaga a declaração anterior por ausência)
89
- "task_items": { "processar-nfe": { "attributes": [{ "key": "numero_nota" }, { "key": "valor", "label": "valor (R$)" }] } }
89
+ "items": { "attributes": [{ "key": "numero_nota" }, { "key": "valor", "label": "valor (R$)" }] }
90
90
  },
91
- "max": 2, // soma dos slots livres das tasks remotas (1..5)
92
- "tasks": ["processar-nfe"] // só tasks de register() com slot livre — a concorrência é por task
91
+ "max": 2 // slots livres de `concurrency` (1..5)
93
92
  }
94
93
  ```
95
94
 
96
- Resposta `PollResponse` = `{ runs: RunDescriptor[] }`, cada `{ id, task, params, form, source, created_at }`.
95
+ Resposta `PollResponse` = `{ runs: RunDescriptor[] }`, cada `{ id, params, form, source, created_at }`.
97
96
 
98
- O `instance.instance_id` é a **mesma identidade** do heartbeat de telemetria (`instance_id`): o servidor correlaciona os dois canais numa tabela `instances` por `(agent_id, instance_id)`. Os dois derivam do mesmo `instanceId` — o robô aparece como um agente só no painel. O próprio poll também marca a presença da instância (`last_poll_at`). Um processo só com tasks de `start()` nunca faz poll.
97
+ O `instance.instance_id` é a **mesma identidade** do heartbeat de telemetria (`instance_id`): o servidor correlaciona os dois canais numa tabela `instances` por `(agent_id, instance_id)`. Os dois derivam do mesmo `instanceId` — o robô aparece como um agente só no painel. O próprio poll também marca a presença da instância (`last_poll_at`). Um processo no modo `start()` nunca faz poll.
99
98
 
100
99
  **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]`.
101
100
 
102
- ### `POST /v1/tasks/runs`
101
+ ### `POST /v1/runs`
103
102
 
104
- Modo `start`: a instância abre a **própria run**. Corpo `RunCreateRequest` — `{ "task": "monitor-pasta", "instance": { /* InstanceInfo, obrigatório */ } }`. Resposta `201` `RunCreateResponse` = o mesmo `RunDescriptor` do poll, com `params: {}`, `form: null` e `source: 'agent'`.
103
+ Modo `start`: a instância abre a **própria run**. Corpo `RunCreateRequest` — `{ "instance": { /* InstanceInfo (com `mode: "always_on"`), obrigatório */ } }`. Resposta `201` `RunCreateResponse` = o mesmo `RunDescriptor` do poll, com `params: {}`, `form: null` e `source: 'agent'`.
105
104
 
106
105
  - A run nasce **`running`** (`started_at`, `claimed_at` e `last_report_at` = agora, `claim_count` 1), sem formulário nem solicitante, vinculada à instância informada. Dali em diante é reportada pelas rotas abaixo exatamente como uma run reivindicada no poll.
107
- - `touchInstance` roda antes do insert (presença); se falhar, a API responde `503` e o SDK insiste com o ladder do poll (o handler só começa quando a run existe). Corpo vazio, sem `instance.instance_id` ou com `task` fora de 1..255 → `422`.
106
+ - `touchInstance` roda antes do insert (presença); se falhar, a API responde `503` e o SDK insiste com o ladder do poll (o handler só começa quando a run existe). Corpo vazio ou sem `instance.instance_id` → `422`.
108
107
  - Sem dedupe: um processo reiniciado em menos de 120 s deixa a run anterior `running` até o sweep marcá-la `AgentTerminated`. O app não oferece "reexecutar" para runs `agent` — ninguém faria poll por elas.
109
108
 
110
- ### `PATCH /v1/tasks/runs/{id}`
109
+ ### `PATCH /v1/runs/{id}`
111
110
 
112
111
  Corpo `RunUpdateRequest`:
113
112
 
@@ -134,7 +133,7 @@ Corpo `RunUpdateRequest`:
134
133
 
135
134
  Ciclo de vida completo da run no servidor (`RunStatus`): `pending → claimed → running → succeeded | failed | canceled` (runs `agent` nascem em `running`). O agente só transiciona para `running`/`succeeded`/`failed` (`RunUpdateStatus`).
136
135
 
137
- ### `POST /v1/tasks/runs/{id}/items`
136
+ ### `POST /v1/runs/{id}/items`
138
137
 
139
138
  Registra em lote os **itens de execução** (a entrega do bot — `hobots.items` no SDK). Insert-only: cada item chega com o status final; um `id` repetido na mesma run é ignorado (retry de rede idempotente).
140
139
 
@@ -172,7 +171,7 @@ Resposta `200` `RunItemsCreateResponse`: `{ "accepted": 1, "duplicates": 0 }`
172
171
  | `422` | payload inválido |
173
172
  | `429` | limite de 10.000 itens por execução atingido (lote inteiro rejeitado) |
174
173
 
175
- ### `POST /v1/tasks/runs/{id}/attachments`
174
+ ### `POST /v1/runs/{id}/attachments`
176
175
 
177
176
  Registra um anexo de imagem e devolve uma **URL presignada de PUT**. O binário nunca passa pela API: o agente sobe direto no S3.
178
177
 
@@ -213,7 +212,7 @@ O PUT no S3 precisa mandar exatamente esses headers — eles fazem parte da assi
213
212
  | `503` | anexos não habilitados nesta instalação (sem bucket S3 configurado) |
214
213
  | `502` | falha ao presignar — o registro é desfeito |
215
214
 
216
- ### `PATCH /v1/tasks/runs/{id}/attachments/{attachmentId}`
215
+ ### `PATCH /v1/runs/{id}/attachments/{attachmentId}`
217
216
 
218
217
  Confirma o upload. Corpo `RunAttachmentConfirmRequest`: `{ "status": "uploaded" | "failed", "size_bytes"?, "width"?, "height"? }`; resposta `{ "ok": true }`, ou `404` se o anexo não existe no projeto da chave.
219
218
 
@@ -223,7 +222,7 @@ Limites de anexo (aplicados no SDK **e** no servidor): PNG/JPEG/WEBP, 5 MB por i
223
222
 
224
223
  ## Onde vive o claim atômico
225
224
 
226
- O `POST /v1/tasks/poll` no app chama `claimPendingRuns()` em
225
+ O `POST /v1/runs/poll` no app chama `claimPendingRuns()` em
227
226
  [`apps/api/lib/tasks/claim.ts`](../../../apps/api/lib/tasks/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.
228
227
 
229
228
  ## Identidade do SDK
@@ -1,27 +1,27 @@
1
- # Tasks
1
+ # Execuções
2
2
 
3
- Uma **task** é o que o robô sabe fazer, identificada por um slug. Cada rodada é uma **execução** (run) na plataforma, com logs, itens, progresso, anexos e resultado. Há dois modos de registrar uma task; tudo o mais (o objeto `hobots`, cancelamento, retry) é igual nos dois.
3
+ O **agente** é o seu robô, e o handler que você passa a `register()`/`start()` é o que ele faz. Cada rodada é uma **execução** (run) na plataforma, com logs, itens, progresso, anexos e resultado. Há dois modos de iniciar o agente; tudo o mais (o objeto `hobots`, cancelamento, retry) é igual nos dois.
4
4
 
5
5
  ## Os dois modos
6
6
 
7
7
  ```js
8
- // sob demanda: uma execução por solicitação do app (formulário com este task_slug ou agendamento)
9
- hobots.register('processar-nfe', async params => { /* … */ }, { concurrency: 2 });
8
+ // sob demanda: uma execução por solicitação do app (formulário do agente ou agendamento)
9
+ hobots.register(async params => { /* … */ }, { concurrency: 2 });
10
10
 
11
11
  // sempre ligado: o robô abre a própria execução e roda o handler (o seu loop) uma vez
12
- hobots.start('monitor-pasta', async () => { /* while (true) { … } */ });
12
+ hobots.start(async () => { /* while (true) { … } */ });
13
13
  ```
14
14
 
15
- - `register` — o robô faz polling na plataforma; cada solicitação vira uma execução com os `params` do formulário. Registre antes de haver solicitações; o polling começa sozinho.
16
- - `start` — ao iniciar, o SDK cria a execução (`POST /v1/tasks/runs`) e roda o handler com `params = {}`. Enquanto a plataforma não responde, insiste com backoff — o handler só começa quando a execução existe. Quando o handler retorna, a execução fecha como `succeeded` (ou `failed` após os retries) e o processo segue vivo pelo heartbeat. **Uma execução por processo** — para "recomeçar", reinicie o robô.
17
- - Os dois coexistem no mesmo processo. Um slug só pode ser registrado uma vez (lança).
15
+ - `register` — o robô faz polling na plataforma; cada solicitação vira uma execução com os `params` do formulário. O polling começa sozinho.
16
+ - `start` — ao iniciar, o SDK cria a execução (`POST /v1/runs`) e roda o handler com `params = {}`. Enquanto a plataforma não responde, insiste com backoff — o handler só começa quando a execução existe. Quando o handler retorna, a execução fecha como `succeeded` (ou `failed` após os retries) e o processo segue vivo pelo heartbeat. **Uma execução por processo** — para "recomeçar", reinicie o robô.
17
+ - **Uma chamada por processo**: `register` ou `start`. A segunda lança (`o agente já foi iniciado`). Dois trabalhos diferentes são dois agentes.
18
18
 
19
19
  > No modo start, lembre do teto de **1000 linhas de log por execução** no servidor: logue novidades e use `hobots.items` para o trabalho feito (até 10.000 itens).
20
20
 
21
21
  ## O handler e o objeto `hobots`
22
22
 
23
23
  ```ts
24
- type TaskHandler<P = Record<string, unknown>> = (params: P, ctx: RunContext) => void | Promise<void>;
24
+ type RunHandler<P = Record<string, unknown>> = (params: P, ctx: RunContext) => void | Promise<void>;
25
25
  ```
26
26
 
27
27
  Dentro do handler — e em **qualquer módulo** chamado por ele — o objeto global `hobots` resolve a execução atual:
@@ -40,15 +40,15 @@ Isso funciona com `concurrency > 1` porque cada execução roda no seu contexto
40
40
 
41
41
  | Membro | Notas |
42
42
  | --- | --- |
43
- | `hobots.log.<debug\|info\|warn\|error\|success>(msg, ...rest)` | Enviado incrementalmente (PATCHes com throttle) e ecoado no terminal. Nunca lança. `hobots.image(...)` no último argumento = print da linha. |
43
+ | `hobots.log.<debug\|info\|warn\|error\|success>(msg, ...rest)` | Enviado incrementalmente (PATCHes com throttle) e ecoado no terminal (`[hobots] INFO …`). Nunca lança. `hobots.image(...)` no último argumento = print da linha. |
44
44
  | `hobots.items` | Ver [Itens](#itens-de-execução-hobotsitems) |
45
45
  | `hobots.progress` | Ver [Progresso](#progresso-hobotsprogress) |
46
- | `hobots.transaction` | Transaction da execução, já correlacionada e finalizada pelo SDK. `startChild(op, descrição)` = uma step no waterfall. |
46
+ | `hobots.transaction` | Transaction da execução (`run`), já correlacionada e finalizada pelo SDK. `startChild(op, descrição)` = uma step no waterfall. |
47
47
  | `hobots.image(imagem, opts?)` | Ver [Imagens](#imagens-screenshots) |
48
48
  | `hobots.captureException(erro, hint?)` | Issue no módulo Issues; `hint.attachment` anexa um print à linha de log |
49
49
  | `hobots.retry(fn, opts?)` | Ver [Retry](#retry) |
50
50
  | `hobots.sleep(ms)` · `hobots.signal` · `hobots.throwIfCanceled()` | Ver [Cancelamento](#cancelamento-cooperativo) |
51
- | `hobots.runId` · `hobots.task` · `hobots.params` | Identidade da execução |
51
+ | `hobots.runId` · `hobots.params` | Identidade da execução |
52
52
 
53
53
  - **Retorno** do handler: **ignorado** (resultados podem conter dados sensíveis). Reporte por logs e itens.
54
54
  - **Erro lançado**: nova tentativa (ver Retry) e, esgotadas, `failed` com tipo, mensagem (cap 10k) e stack (cap 20k), passando pelo scrub de segredos.
@@ -57,7 +57,7 @@ Isso funciona com `concurrency > 1` porque cada execução roda no seu contexto
57
57
 
58
58
  ## Itens de execução (`hobots.items`)
59
59
 
60
- A **entrega do robô**: cada item processado, com status e atributos livres (viram colunas na aba de itens da execução no app), totalizadores na solicitação.
60
+ A **entrega do robô**: cada item processado, com status e atributos livres (viram colunas na aba de itens da execução no app), totalizadores no trabalho.
61
61
 
62
62
  ```js
63
63
  hobots.items.succeeded({ numero_nota: nota.numero, cnpj: nota.cnpj }, { id: nota.numero });
@@ -96,20 +96,20 @@ const resultado = await hobots.items.each(
96
96
  - Só **cancelamento, timeout e shutdown** interrompem o loop: o `RunCanceled` sobe, o item em curso não é registrado e o progresso não avança.
97
97
  - Aceita `Iterable` ou `AsyncIterable` (sem `length`, nenhum total é declarado).
98
98
 
99
- > Se o handler da task termina com o progresso parado antes do total declarado (ex.: 45/76), o SDK deixa um aviso no log da execução — e a Central sinaliza o desencontro entre processados e itens reportados.
99
+ > Se o handler termina com o progresso parado antes do total declarado (ex.: 45/76), o SDK deixa um aviso no log da execução — e a Central sinaliza o desencontro entre processados e itens reportados.
100
100
 
101
101
  ### Colunas da aba de itens
102
102
 
103
- As colunas vêm do código, não do formulário — valem para `register()` (com ou sem formulário) e `start()`:
103
+ As colunas vêm do código, não do formulário — é **uma declaração só, do agente**, válida para `register()` (com ou sem formulário) e `start()`:
104
104
 
105
105
  ```js
106
- register('processar-nfe', handler, {
106
+ hobots.register(handler, {
107
107
  items: { columns: ['numero_nota', { key: 'cnpj', label: 'CNPJ' }, { key: 'valor', label: 'valor (R$)' }] },
108
108
  });
109
109
  ```
110
110
 
111
111
  - Ordem da lista = ordem das colunas; até 12; chave com letras, números, `.`, `-`, `_` (inválidas são descartadas com aviso em `debug`).
112
- - A declaração vai na presença da instância (poll / abertura de run). No app, **Configurações → agente → itens da execução** pode personalizar as colunas (e restaurar as do código).
112
+ - A declaração vai na presença da instância (`instance.items` no poll / abertura de run). No app, **Configurações → agente → itens da execução** pode personalizar as colunas (e restaurar as do código).
113
113
  - Sem declaração nem personalização, o app mostra as chaves encontradas nos próprios payloads.
114
114
 
115
115
  ## Progresso (`hobots.progress`)
@@ -156,17 +156,17 @@ try {
156
156
  hobots.captureException(erro, { attachment: hobots.image(await page.screenshot(), { caption: 'tela no momento do erro' }) });
157
157
  ```
158
158
 
159
- O SDK não tira o print: aceita bytes (`Uint8Array`/`ArrayBuffer`/`Buffer`) ou um caminho de arquivo, crus ou embrulhados por `hobots.image(bytes, { caption?, contentType? })` (só o embrulho leva legenda; em `log.*` ele é obrigatório para distinguir a imagem de um argumento comum). O evento entra na hora com o id do anexo; o upload sobe em segundo plano direto para o S3 (URL presignada) e **nunca lança nem bloqueia** — falhas viram um aviso no log da execução. Limites: PNG/JPEG/WEBP, 5 MB, 20 por execução, 4 por evento. Fora de uma execução não há onde pendurar a imagem (descartada com aviso). O `hobots.attach` das versões anteriores foi removido nesta API.
159
+ O SDK não tira o print: aceita bytes (`Uint8Array`/`ArrayBuffer`/`Buffer`) ou um caminho de arquivo, crus ou embrulhados por `hobots.image(bytes, { caption?, contentType? })` (só o embrulho leva legenda; em `log.*` ele é obrigatório para distinguir a imagem de um argumento comum). O evento entra na hora com o id do anexo; o upload sobe em segundo plano direto para o S3 (URL presignada) e **nunca lança nem bloqueia** — falhas viram um aviso no log da execução. Limites: PNG/JPEG/WEBP, 5 MB, 20 por execução, 4 por evento. Fora de uma execução não há onde pendurar a imagem (descartada com aviso).
160
160
 
161
161
  ## Cancelamento cooperativo
162
162
 
163
- Cancelar a solicitação no app **não mata** o handler: o SDK detecta (o PATCH periódico recebe 409), dispara `hobots.signal` e o próximo **ponto de parada** lança `RunCanceled`:
163
+ Cancelar o trabalho no app **não mata** o handler: o SDK detecta (o PATCH periódico recebe 409), dispara `hobots.signal` e o próximo **ponto de parada** lança `RunCanceled`:
164
164
 
165
165
  - `hobots.items.*`, `hobots.progress.total/advance/set`, `hobots.transaction.startChild()`, `hobots.sleep()` e cada tentativa de `hobots.retry()`.
166
166
  - **Nunca `hobots.log.*`** — log em `finally` de limpeza não pode explodir.
167
167
 
168
168
  ```js
169
- hobots.register('processar-nfe', async params => {
169
+ hobots.register(async params => {
170
170
  const browser = await abrir();
171
171
  try {
172
172
  for (const nota of params.notas) {
@@ -200,7 +200,7 @@ const dados = await hobots.retry(() => portal.buscar(nota), { retries: 3, delayM
200
200
 
201
201
  ## Concorrência (`register`)
202
202
 
203
- `concurrency` (1..5, default 1) por task. O agente só pede à plataforma o que consegue rodar (tasks com slot livre, `max` = soma dos slots, teto 5) e entra em **drain mode** — re-pesquisa imediatamente enquanto vem trabalho; sem trabalho, espera `pollIntervalMs`.
203
+ `concurrency` (1..5, default 1) é **do agente**: quantas solicitações ele atende ao mesmo tempo. O agente só pede à plataforma o que consegue rodar (`max` = slots livres, teto 5) e entra em **drain mode** — re-pesquisa imediatamente enquanto vem trabalho; sem trabalho, espera `pollIntervalMs`.
204
204
 
205
205
  ## Timeout
206
206
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hobots",
3
- "version": "3.4.0",
4
- "description": "SDK JavaScript da Hobots — tudo é uma task: register() atende solicitações da plataforma, start() roda sempre e reporta logs, itens e progresso. Credenciais por env, heartbeat sempre ligado, objeto global `hobots`.",
3
+ "version": "4.0.0",
4
+ "description": "SDK JavaScript da Hobots — o agente é a própria tarefa: register() atende solicitações da plataforma (sob demanda), start() roda sempre ligado e reporta logs, itens e progresso. Credenciais por env, heartbeat sempre ligado, objeto global `hobots`.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "license": "MIT",