hobots 3.4.0 → 4.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.
package/docs/concepts.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Conceitos
2
2
 
3
- Esta página explica **o que cada coisa é** — o vocabulário que as outras páginas usam: task, execução, item, evento, issue, transaction, step, heartbeat, instância, claim. As outras páginas mostram **como usar**; aqui está o **por que existe**.
3
+ Esta página explica **o que cada coisa é** — o vocabulário que as outras páginas usam: agente, execução, item, evento, issue, transaction, step, heartbeat, instância, claim. As outras páginas mostram **como usar**; aqui está o **por que existe**.
4
4
 
5
5
  Defaults numéricos vivem em [Tempos e intervalos](./api-reference.md#tempos-e-intervalos) e nas [tabelas de opções](./api-reference.md#tabelas-de-opções).
6
6
 
@@ -8,14 +8,14 @@ Defaults numéricos vivem em [Tempos e intervalos](./api-reference.md#tempos-e-i
8
8
 
9
9
  | Conceito | Em uma frase | Quando você usa |
10
10
  | --- | --- | --- |
11
- | [Task](#task) | O que o robô sabe fazer, por slug | `hobots.register` / `hobots.start` |
11
+ | [Agente](#agente) | O seu robô: um processo, uma coisa que ele faz | `hobots.register` / `hobots.start` |
12
12
  | [Modo sob demanda](#modo-sob-demanda-register) | Uma execução por solicitação do app | O trabalho é disparado por alguém (formulário, agendamento) |
13
13
  | [Modo sempre ligado](#modo-sempre-ligado-start) | O robô abre a própria execução e roda o seu loop | O robô vive rodando e você só quer ver o que ele faz |
14
- | [Execução (run)](#execução-run) | Uma rodada de uma task, com logs, itens e resultado | A unidade de tudo em Solicitações |
14
+ | [Execução (run)](#execução-run) | Uma rodada do agente, com logs, itens e resultado | A unidade de tudo em Trabalhos |
15
15
  | [Item](#item) | Uma unidade de trabalho entregue | Cada nota, boleto, arquivo processado |
16
16
  | [Progresso](#progresso) | Quantos de quantos | Barra e tempo restante no app |
17
17
  | [Log da execução](#log-da-execução) | Narração do que a execução fez | `hobots.log` — nunca lança |
18
- | [Anexo](#anexo) | Um print pendurado numa linha de log | "Como estava a tela?" |
18
+ | [Anexo](#anexo) | Um print pendurado num log, item ou step | "Como estava a tela?" |
19
19
  | [Cancelamento cooperativo](#cancelamento-cooperativo) | O app pede, o handler para no próximo ponto de parada | Automático em items/progress/steps/sleep/retry |
20
20
  | [Retry](#retry) | Tentar de novo na mesma execução | Instabilidade passageira |
21
21
  | [Evento e issue](#evento-e-issue) | A foto de um erro, agrupada em issue | `hobots.captureException` e erros não tratados |
@@ -31,39 +31,39 @@ Defaults numéricos vivem em [Tempos e intervalos](./api-reference.md#tempos-e-i
31
31
 
32
32
  | No SDK | No app |
33
33
  | --- | --- |
34
- | **Agente** — dono do Client Secret (`hb_…`) que autentica tudo | **Configurações → Agentes** |
35
- | **Instância** — o par `(agente, instanceId)`; heartbeat e tasks convergem nela | Módulo **Instâncias** (uptime, recursos da máquina, performance) |
36
- | **Task / execução** — `register('slug', …)` casa com o `task_slug` do formulário; `start('slug', …)` abre a própria execução | Módulo **Solicitações** (formulários em **Configurações → Formulários**) |
34
+ | **Agente** — dono do Client Secret (`hb_…`) que autentica tudo; o que ele faz é o handler de `register()`/`start()` | **Configurações → Agentes** (o formulário do agente fica aqui) |
35
+ | **Instância** — o par `(agente, instanceId)`; heartbeat e execuções convergem nela, com o modo (sob demanda / sempre ligado) | Módulo **Instâncias** (uptime, recursos da máquina, performance) |
36
+ | **Execução** — cada solicitação atendida por `register()`, ou a execução que `start()` abre sozinho | Módulo **Trabalhos** |
37
37
  | **Evento** agrupado em **issue** — `captureException` e erros não tratados | Módulo **Issues** |
38
38
  | **Transaction / steps** — `hobots.transaction` | Aba de performance da execução |
39
39
 
40
40
  ---
41
41
 
42
- ## Task
42
+ ## Agente
43
43
 
44
- Uma **task** é uma capacidade nomeada do robô — "processar NF-e", "monitorar a pasta de entrada" — identificada por um **slug** estável. Registrar uma task é a única forma de configurar o SDK: não há `init()`. A primeira chamada a `register()`/`start()` lê as credenciais do ambiente, liga o heartbeat e a captura de erros, e define as opções do processo (`environment`, `release`, `tags`…). Um slug só pode ser registrado uma vez.
44
+ O **agente** é o seu robô: um programa que faz **uma coisa** — "processar NF-e", "monitorar a pasta de entrada". Na plataforma ele é identificado pelo Client Secret, não por um nome no código: o processo que sobe com aquele secret *é* o agente, e o handler que você passa a `register()`/`start()` *é* a tarefa. Essa chamada é a única forma de configurar o SDK: não há `init()`. Ela lê as credenciais do ambiente, liga o heartbeat e a captura de erros, define as opções (`environment`, `release`, `tags`, `items`…) e diz como o robô trabalha. **Uma chamada por processo** — a segunda lança. Um robô que faz duas coisas são dois agentes, cada um com o seu processo e o seu Client Secret.
45
45
 
46
46
  ## Modo sob demanda (`register`)
47
47
 
48
- O robô fica **online aguardando**: faz polling na plataforma e, quando alguém cria uma solicitação para aquele slug (pelo formulário ou por um agendamento), executa o handler com os `params` do formulário. É o modo para trabalho que **alguém pede**. Várias solicitações podem rodar em paralelo (`concurrency`).
48
+ O robô fica **online aguardando**: faz polling na plataforma e, quando alguém cria uma solicitação para o agente (pelo formulário do agente ou por um agendamento), executa o handler com os `params` do formulário. É o modo para trabalho que **alguém pede**. Várias solicitações podem rodar em paralelo (`concurrency`). No painel a instância aparece como "sob demanda".
49
49
 
50
50
  ## Modo sempre ligado (`start`)
51
51
 
52
- O robô **não espera ninguém**: ao iniciar, abre a própria execução na plataforma (`source: agent`, sem formulário nem solicitante) e roda o handler dentro dela. O handler é o seu loop — um `while (true)` que vigia uma pasta, uma fila, um portal. A plataforma recebe logs, itens e progresso enquanto ele roda. Quando o handler retorna, a execução fecha; o processo continua vivo pelo heartbeat, então a instância segue online até `hobots.close()`. É **uma execução por processo**: para recomeçar, reinicie o robô. Como o servidor guarda no máximo 1000 linhas de log por execução, o trabalho feito vai em itens, não em log.
52
+ O robô **não espera ninguém**: ao iniciar, abre a própria execução na plataforma (`source: agent`, sem formulário nem solicitante) e roda o handler dentro dela. O handler é o seu loop — um `while (true)` que vigia uma pasta, uma fila, um portal. A plataforma recebe logs, itens e progresso enquanto ele roda. Quando o handler retorna, a execução fecha; o processo continua vivo pelo heartbeat, então a instância segue online até `hobots.close()`. É **uma execução por processo**: para recomeçar, reinicie o robô. Como o servidor guarda no máximo 1000 linhas de log por execução, o trabalho feito vai em itens, não em log. No painel a instância aparece como "sempre ligado".
53
53
 
54
54
  ## Execução (run)
55
55
 
56
- Uma **execução** é uma rodada concreta de uma task — no app, uma solicitação. Ciclo de vida no servidor:
56
+ Uma **execução** é uma rodada concreta do agente — no app, um trabalho. Ciclo de vida no servidor:
57
57
 
58
58
  ```
59
59
  pending → claimed → running → succeeded | failed | canceled
60
60
  ```
61
61
 
62
- No modo sob demanda a execução nasce `pending` (alguém pediu), vira `claimed` quando um agente a [reivindica](#claim) e `running` quando o handler começa. No modo sempre ligado ela já nasce `running`, criada pelo próprio robô. O agente só transiciona para `running`, `succeeded` e `failed`; `canceled` vem de alguém cancelando no app. Cada execução tem a sua transaction de performance, seus logs, itens e progresso — e o objeto `hobots` aponta para a execução **atual**, em qualquer módulo, graças ao contexto async do Node.
62
+ No modo sob demanda a execução nasce `pending` (alguém pediu), vira `claimed` quando um agente a [reivindica](#claim) e `running` quando o handler começa. No modo sempre ligado ela já nasce `running`, criada pelo próprio robô. O agente só transiciona para `running`, `succeeded` e `failed`; `canceled` vem de alguém cancelando no app. Cada execução tem a sua transaction de performance (`run`), seus logs, itens e progresso — e o objeto `hobots` aponta para a execução **atual**, em qualquer módulo, graças ao contexto async do Node.
63
63
 
64
64
  ## Item
65
65
 
66
- Um **item** é uma unidade de trabalho que a execução entregou: uma nota baixada, um boleto emitido, um arquivo movido. Tem status (`succeeded`, `failed` = falha técnica, `occurrence` = ocorrência de negócio, como "nota não encontrada") e um `payload` de atributos livres que viram colunas na aba de itens da solicitação. O `id` é a identidade de negócio do item na execução — repetido, é ignorado: é isso que torna retries idempotentes. Diferente do log, o item é **dado estruturado**: dá para contar, filtrar e conferir.
66
+ Um **item** é uma unidade de trabalho que a execução entregou: uma nota baixada, um boleto emitido, um arquivo movido. Tem status (`succeeded`, `failed` = falha técnica, `occurrence` = ocorrência de negócio, como "nota não encontrada") e um `payload` de atributos livres que viram colunas na aba de itens da execução — as colunas são declaradas no código, uma vez, pela opção `items` do agente. O `id` é a identidade de negócio do item na execução — repetido, o servidor atualiza em vez de duplicar: é isso que torna retries idempotentes. Diferente do log, o item é **dado estruturado**: dá para contar, filtrar e conferir.
67
67
 
68
68
  ## Progresso
69
69
 
@@ -71,11 +71,11 @@ O **progresso** é "quantos de quantos" (`current/total`). Declare o total antes
71
71
 
72
72
  ## Log da execução
73
73
 
74
- O **log da execução** narra o que aconteceu, em linhas com nível (`debug`, `info`, `warning`, `error`, `success`), enviadas incrementalmente e visíveis no app quase em tempo real. Ele é o único membro de `hobots` que **nunca lança**, justamente para poder ir num `finally` de limpeza. O servidor guarda até 1000 linhas por execução.
74
+ O **log da execução** narra o que aconteceu, em linhas com nível (`debug`, `info`, `warning`, `error`, `success`), enviadas incrementalmente e visíveis no app quase em tempo real (e ecoadas no terminal como `[hobots] INFO …`). Ele é o único membro de `hobots` que **nunca lança**, justamente para poder ir num `finally` de limpeza. O servidor guarda até 1000 linhas por execução.
75
75
 
76
76
  ## Anexo
77
77
 
78
- Um **anexo** é uma imagem — na prática, um print — vinculada a uma linha de log. Responde "como estava a tela?" quando texto não basta. Quem captura é o seu código (Playwright, Puppeteer, um arquivo); o SDK sobe direto para o S3 por URL presignada, em segundo plano, sem atrasar o log. Enquanto sobe, o app mostra "enviando imagem…".
78
+ Um **anexo** é uma imagem — na prática, um print — vinculada ao evento que ela ilustra: uma linha de log, um item ou uma step. Responde "como estava a tela?" quando texto não basta. Quem captura é o seu código (Playwright, Puppeteer, um arquivo); o SDK sobe direto para o S3 por URL presignada, em segundo plano, sem atrasar o log. Enquanto sobe, o app mostra "enviando imagem…".
79
79
 
80
80
  ## Cancelamento cooperativo
81
81
 
@@ -91,7 +91,7 @@ Um **evento** é a foto de um erro: tipo, mensagem, stack trace, tags, ambiente,
91
91
 
92
92
  ## Transaction
93
93
 
94
- Uma **transaction** é uma unidade de trabalho **medida**: começa, termina, e o que fica é quanto tempo levou. Cada execução tem exatamente uma, aberta e finalizada pelo SDK (`hobots.transaction`), correlacionada à execução pelo `run_id`. Você não cria transactions — só adiciona etapas.
94
+ Uma **transaction** é uma unidade de trabalho **medida**: começa, termina, e o que fica é quanto tempo levou. Cada execução tem exatamente uma, chamada `run` (op `hobots.run`), aberta e finalizada pelo SDK (`hobots.transaction`), correlacionada à execução pelo `run_id`. Você não cria transactions — só adiciona etapas.
95
95
 
96
96
  ## Step
97
97
 
@@ -103,7 +103,7 @@ O **heartbeat** é o batimento "estou vivo", a cada 30 s. O servidor conhece a c
103
103
 
104
104
  ## instanceId
105
105
 
106
- A identidade da **instância** do robô: qual VM, qual container. Vem da env `HOBOTS_INSTANCE_ID` (ou da opção; sem os dois, o hostname) e deve ser **estável entre reinícios** — um valor novo a cada boot vira uma frota de robôs fantasmas. É a mesma identidade no heartbeat, no poll e na criação de execuções: um agente só no painel.
106
+ A identidade da **instância** do robô: qual VM, qual container. Vem da env `HOBOTS_INSTANCE_ID` (ou da opção; sem os dois, o hostname) e deve ser **estável entre reinícios** — um valor novo a cada boot vira uma frota de robôs fantasmas. É a mesma identidade no heartbeat, no poll e na criação de execuções: um agente só no painel. A presença da instância também declara o **modo** (`on_demand` / `always_on`) e as colunas de itens do agente.
107
107
 
108
108
  ## Release e environment
109
109
 
@@ -111,7 +111,7 @@ A identidade da **instância** do robô: qual VM, qual container. Vem da env `HO
111
111
 
112
112
  ## Claim
113
113
 
114
- O **claim** é o ato de um agente reivindicar uma execução pendente. É **server-side e atômico** (`FOR UPDATE SKIP LOCKED`): dois agentes que fazem poll no mesmo instante recebem conjuntos disjuntos — nenhum lock vive no seu processo. O agente pede só o que consegue rodar (tasks com slot livre), então nunca reivindica trabalho para deixar parado.
114
+ O **claim** é o ato de um agente reivindicar uma execução pendente. É **server-side e atômico** (`FOR UPDATE SKIP LOCKED`): dois agentes que fazem poll no mesmo instante recebem conjuntos disjuntos — nenhum lock vive no seu processo. O agente pede só o que consegue rodar (`max` = slots livres de `concurrency`), então nunca reivindica trabalho para deixar parado.
115
115
 
116
116
  ## Drain mode
117
117
 
@@ -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
 
@@ -162,7 +161,7 @@ Resposta `200` `RunItemsCreateResponse`: `{ "accepted": 1, "duplicates": 0 }`
162
161
 
163
162
  - **Run cancelada/terminal aceita itens** (200): item é fato de trabalho já feito — mesmo racional do log tardio. Esta rota não tem 409.
164
163
  - O insert e os **contadores materializados** da run (`items_total/succeeded/failed/occurrence`, que alimentam os totalizadores no app) saem num único statement atômico no servidor.
165
- - Retry `[1s, 4s, 16s]` para rede/5xx; `404` = servidor sem o recurso (o SDK degrada em silêncio); `429` = cap de **10.000 itens por run** (permanente — o SDK descarta com um aviso).
164
+ - Retry `[1s, 4s, 16s]` para rede/5xx; `404` = servidor sem o recurso (o SDK degrada em silêncio); `402` = armazenamento do plano esgotado. Não há teto de itens por run.
166
165
 
167
166
  | Status | Quando |
168
167
  | --- | --- |
@@ -170,9 +169,9 @@ Resposta `200` `RunItemsCreateResponse`: `{ "accepted": 1, "duplicates": 0 }`
170
169
  | `404` | execução inexistente ou de outro projeto |
171
170
  | `413` | corpo acima de 1 MB |
172
171
  | `422` | payload inválido |
173
- | `429` | limite de 10.000 itens por execução atingido (lote inteiro rejeitado) |
172
+ | `402` | armazenamento do plano esgotado (`storage_limit`) |
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
- > 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).
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 (sem teto por execução — só o armazenamento do plano limita).
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 });
@@ -68,7 +68,7 @@ hobots.items.add({ status: 'succeeded', id: '11', payload: { … } });
68
68
 
69
69
  - Chamadas síncronas e bufferizadas (lotes de até 500); o SDK drena o buffer antes do reporte final.
70
70
  - `id` é a identidade de negócio do item na execução — repetido, **vale o último reporte** (o servidor faz upsert: uma conta que falhou numa tentativa e deu certo no retry fica como sucesso; retry de rede é no-op). Sem `id`, cada chamada é um item novo.
71
- - Limites: 10.000 itens/execução, payload 16 KB, mensagem 2.000 caracteres. Uma execução cancelada ainda aceita os itens já bufferizados (trabalho feito).
71
+ - Limites: sem teto de itens por execução (só o armazenamento do plano), payload 16 KB, mensagem 2.000 caracteres. Uma execução cancelada ainda aceita os itens já bufferizados (trabalho feito).
72
72
  - Cada chamada é um **ponto de parada cooperativo** (lança `RunCanceled` se a execução foi cancelada).
73
73
 
74
74
  ### Processar uma lista inteira (`hobots.items.each`)
@@ -96,22 +96,50 @@ 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
+ ## Entregar enquanto processa (`hobots.flush()`)
116
+
117
+ A transaction da execução só é enviada quando a run termina — num agente sempre ligado (`start()`) isso é nunca. `hobots.flush()` deixa o **bot** decidir quando entregar o que já fez:
118
+
119
+ - cada step **raiz** já finalizada (com todas as filhas; as filhas ainda abertas são fechadas) vai numa transaction parcial da mesma run (`op` `hobots.run.flush`, `event_id` novo, até 200 steps cada) e sai da memória do SDK; steps raiz ainda abertas ficam para o próximo flush;
120
+ - itens, logs e progresso pendentes saem já, sem esperar o throttle;
121
+ - aguarda a entrega com teto (`timeoutMs`, default 15 s) e **nunca lança** — pode ir num `finally`.
122
+
123
+ ```js
124
+ hobots.start(async () => {
125
+ while (true) {
126
+ const lote = await fila.pegar();
127
+ if (lote.length) {
128
+ const rodada = hobots.transaction.startChild('rodada', `${lote.length} pedido(s)`);
129
+ try {
130
+ for (const pedido of lote) await processar(pedido, rodada); // steps filhas + hobots.items.*
131
+ } finally {
132
+ rodada.finish();
133
+ await hobots.flush(); // waterfall e itens desta rodada já na Central
134
+ }
135
+ }
136
+ await hobots.sleep(6_000);
137
+ }
138
+ });
139
+ ```
140
+
141
+ Fila vazia → não abra step: cada step também vira duas linhas (START/FINISH) no log da execução, que guarda no máximo 1000.
142
+
115
143
  ## Progresso (`hobots.progress`)
116
144
 
117
145
  ```js
@@ -156,17 +184,17 @@ try {
156
184
  hobots.captureException(erro, { attachment: hobots.image(await page.screenshot(), { caption: 'tela no momento do erro' }) });
157
185
  ```
158
186
 
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.
187
+ 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
188
 
161
189
  ## Cancelamento cooperativo
162
190
 
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`:
191
+ 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
192
 
165
193
  - `hobots.items.*`, `hobots.progress.total/advance/set`, `hobots.transaction.startChild()`, `hobots.sleep()` e cada tentativa de `hobots.retry()`.
166
194
  - **Nunca `hobots.log.*`** — log em `finally` de limpeza não pode explodir.
167
195
 
168
196
  ```js
169
- hobots.register('processar-nfe', async params => {
197
+ hobots.register(async params => {
170
198
  const browser = await abrir();
171
199
  try {
172
200
  for (const nota of params.notas) {
@@ -200,7 +228,7 @@ const dados = await hobots.retry(() => portal.buscar(nota), { retries: 3, delayM
200
228
 
201
229
  ## Concorrência (`register`)
202
230
 
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`.
231
+ `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
232
 
205
233
  ## Timeout
206
234
 
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.1.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",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../protocol-limits.json","../src/wire-limits.ts"],"sourcesContent":["{\n \"MAX_ITEMS_PER_ENVELOPE\": 50,\n \"HEARTBEAT_DEFAULT_INTERVAL_MS\": 30000,\n \"HEARTBEAT_MIN_INTERVAL_MS\": 1000,\n \"HEARTBEAT_MAX_INTERVAL_MS\": 900000,\n \"MAX_CLAIM_PER_POLL\": 5,\n \"MAX_LOG_MESSAGE_CHARS\": 10000,\n \"MAX_LOGS_PER_PATCH\": 100,\n \"MAX_ERROR_MESSAGE_CHARS\": 10000,\n \"MAX_ERROR_STACK_CHARS\": 20000,\n \"MAX_ITEMS_PER_RUN\": 10000,\n \"MAX_ITEMS_PER_REQUEST\": 500,\n \"MAX_ITEM_MESSAGE_CHARS\": 2000,\n \"MAX_ITEM_PAYLOAD_BYTES\": 16384,\n \"MAX_ITEM_COLUMNS\": 12,\n \"MAX_ITEM_COLUMN_LABEL_CHARS\": 60,\n \"MAX_ATTACHMENT_BYTES\": 5242880,\n \"MAX_ATTACHMENTS_PER_RUN\": 20,\n \"MAX_ATTACHMENTS_PER_LOG_ENTRY\": 4,\n \"MAX_CAPTION_CHARS\": 200,\n \"MAX_IMAGE_DIMENSION_PX\": 20000\n}\n","import limits from '../protocol-limits.json';\n\n/**\n * Limites do wire SDK ↔ servidor — fonte ÚNICA (protocol-limits.json): o SDK\n * Node lê daqui, a @hobots/api importa de `hobots/protocol` e o SDK Python\n * confere a paridade num teste que lê o mesmo JSON. Mudar um limite = mudar o\n * JSON (e, no Python, a constante espelhada — o teste acusa).\n */\nexport const WIRE_LIMITS = limits as Readonly<typeof limits>;\nexport type WireLimits = typeof WIRE_LIMITS;\n"],"mappings":";AAAA;AAAA,EACE,wBAA0B;AAAA,EAC1B,+BAAiC;AAAA,EACjC,2BAA6B;AAAA,EAC7B,2BAA6B;AAAA,EAC7B,oBAAsB;AAAA,EACtB,uBAAyB;AAAA,EACzB,oBAAsB;AAAA,EACtB,yBAA2B;AAAA,EAC3B,uBAAyB;AAAA,EACzB,mBAAqB;AAAA,EACrB,uBAAyB;AAAA,EACzB,wBAA0B;AAAA,EAC1B,wBAA0B;AAAA,EAC1B,kBAAoB;AAAA,EACpB,6BAA+B;AAAA,EAC/B,sBAAwB;AAAA,EACxB,yBAA2B;AAAA,EAC3B,+BAAiC;AAAA,EACjC,mBAAqB;AAAA,EACrB,wBAA0B;AAC5B;;;ACbO,IAAM,cAAc;","names":[]}