hobots 4.0.0 → 4.2.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
@@ -91,7 +91,7 @@ A opção passada em `register()`/`start()` tem precedência sobre a env — út
91
91
 
92
92
  **Um processo = um agente = uma chamada.** A segunda chamada a `register()`/`start()` lança (`o agente já foi iniciado`). Um robô que precisa monitorar uma pasta *e* atender solicitações são dois agentes — dois processos, cada um com o seu Client Secret.
93
93
 
94
- > **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).
94
+ > **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 teto por execução (só o armazenamento do plano limita). As steps de uma run que não termina só chegam à Central com `hobots.flush()` — chame-o ao fim de cada rodada que processou itens.
95
95
 
96
96
  ---
97
97
 
@@ -103,12 +103,14 @@ A opção passada em `register()`/`start()` tem precedência sobre a env — út
103
103
  | `hobots.items.succeeded(payload?, { id, image }?)` · `.failed(msg, payload?, …)` · `.occurrence(msg, payload?, …)` · `.add({...})` | **A entrega do robô**: cada item processado, com status e atributos (viram colunas no app — declare-as com a opção `items: { columns: [...] }` do `register`/`start`). `image` pendura um print no item. Bufferizado, em lotes. |
104
104
  | `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
105
  | `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
+ | `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. |
106
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. |
107
108
  | `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. |
108
109
  | `hobots.captureException(erro, hint?)` | Abre uma **issue** (módulo Issues). `hint.attachment` anexa um print à linha de log correspondente. |
109
110
  | `hobots.retry(fn, { retries = 3, delayMs = 1_000, shouldRetry? })` | async-retry ciente do cancelamento da execução. `delayMs` aceita função `(tentativa) => ms`. |
110
111
  | `hobots.sleep(ms)` | Espera **cancelável**: rejeita com `RunCanceled` se a execução for cancelada. |
111
112
  | `hobots.signal` · `hobots.throwIfCanceled()` | Cancelamento cooperativo manual, para controle fino. |
113
+ | `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. |
112
114
  | `hobots.runId` · `hobots.params` | Identidade da execução atual. |
113
115
  | `hobots.register(...)` · `hobots.start(...)` · `hobots.close(timeoutMs?)` | Ciclo de vida do processo. |
114
116
 
@@ -116,6 +118,35 @@ O handler também recebe o mesmo contexto como 2º argumento (`(params, ctx) =>
116
118
 
117
119
  ---
118
120
 
121
+ ## Arquivos do formulário
122
+
123
+ 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.
124
+
125
+ ```js
126
+ import { hobots } from 'hobots';
127
+
128
+ hobots.register(async () => {
129
+ // campo único: o caminho do arquivo baixado (pasta temporária da execução)
130
+ const planilha = await hobots.files.download('planilha');
131
+ await importarPlanilha(planilha);
132
+
133
+ // campo "vários arquivos": um caminho por arquivo, na ordem, na pasta escolhida
134
+ const notas = await hobots.files.downloadAll('notas', { dest: './entrada' });
135
+ for (const nota of notas) await lancarNota(nota);
136
+
137
+ // ou direto em memória (Buffer) — a cópia temporária é apagada
138
+ const contrato = await hobots.files.read('contrato');
139
+ });
140
+ ```
141
+
142
+ - **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`.
143
+ - **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`).
144
+ - **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.
145
+ - `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).
146
+ - Respeita o cancelamento: uma execução cancelada no meio do download lança `RunCanceled` e não consome o arquivo.
147
+
148
+ ---
149
+
119
150
  ## Cancelamento automático
120
151
 
121
152
  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`**:
@@ -188,6 +219,7 @@ Sem handler próprio de sinal, um `SIGINT`/`SIGTERM` (ou erro fatal) com execuç
188
219
 
189
220
  ## Changelog resumido
190
221
 
222
+ - **4.2.0** — **arquivos do formulário**: `hobots.files` (`list`, `get`, `download`, `downloadAll`, `read`) e `isFileRef`; tipos `FileRef`, `FileStorage`, `FileDownloadResponse` em `hobots/protocol`.
191
223
  - **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).
192
224
  - **3.4.0** — `hobots.attach()` removido; o print vai junto do evento: `hobots.image(...)` no último argumento de `log.*`, `{ image }` em itens e steps.
193
225
  - **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.
@@ -9,7 +9,6 @@ var protocol_limits_default = {
9
9
  MAX_LOGS_PER_PATCH: 100,
10
10
  MAX_ERROR_MESSAGE_CHARS: 1e4,
11
11
  MAX_ERROR_STACK_CHARS: 2e4,
12
- MAX_ITEMS_PER_RUN: 1e4,
13
12
  MAX_ITEMS_PER_REQUEST: 500,
14
13
  MAX_ITEM_MESSAGE_CHARS: 2e3,
15
14
  MAX_ITEM_PAYLOAD_BYTES: 16384,
@@ -28,4 +27,4 @@ var WIRE_LIMITS = protocol_limits_default;
28
27
  export {
29
28
  WIRE_LIMITS
30
29
  };
31
- //# sourceMappingURL=chunk-5WIIACWC.js.map
30
+ //# sourceMappingURL=chunk-HKCDMUA3.js.map
@@ -0,0 +1 @@
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_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,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;;;ACZO,IAAM,cAAc;","names":[]}