hobots 3.3.0 → 3.4.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.
@@ -204,7 +204,7 @@ type RunItemStatus = 'succeeded' | 'failed' | 'occurrence';
204
204
  interface RunItemInput {
205
205
  /**
206
206
  * Identidade do item dentro da run — id de negócio do dev (ex.: nº da nota)
207
- * ou uuid4-hex gerado pelo SDK. Repetido na mesma run = ignorado (primeiro vence).
207
+ * ou uuid4-hex gerado pelo SDK. Repetido na mesma run = upsert (vale o último reporte).
208
208
  */
209
209
  id: string;
210
210
  /** Ordem de emissão no agente (contador monotônico por run) */
@@ -216,6 +216,8 @@ interface RunItemInput {
216
216
  payload?: Record<string, unknown>;
217
217
  /** ISO 8601 — relógio do agente, apenas para exibição */
218
218
  ts?: string;
219
+ /** Ids de anexo (print do item) — opcional; mesmo teto por entrada das linhas de log */
220
+ attachments?: string[];
219
221
  }
220
222
  /** POST /v1/tasks/runs/{id}/items — aceito mesmo com a run cancelada/terminal */
221
223
  interface RunItemsCreateRequest {
@@ -231,13 +233,13 @@ interface RunItemsCreateResponse {
231
233
  type RunAttachmentContentType = 'image/png' | 'image/jpeg' | 'image/webp';
232
234
  /** POST /v1/tasks/runs/{id}/attachments */
233
235
  interface RunAttachmentCreateRequest {
234
- /** Id do anexo gerado pelo agente (32 hex) — já referenciado na linha de log */
236
+ /** Id do anexo gerado pelo agente (32 hex) — já referenciado no log/item/step que o carrega */
235
237
  id: string;
236
238
  content_type: RunAttachmentContentType;
237
239
  /** Tamanho exato do binário; o PUT presignado é assinado com este Content-Length */
238
240
  size_bytes: number;
239
241
  caption?: string;
240
- /** ISO 8601 — ts da linha de log que referencia o anexo */
242
+ /** ISO 8601 — ts do evento (log/item/step) que referencia o anexo */
241
243
  log_ts?: string;
242
244
  width?: number;
243
245
  height?: number;
@@ -204,7 +204,7 @@ type RunItemStatus = 'succeeded' | 'failed' | 'occurrence';
204
204
  interface RunItemInput {
205
205
  /**
206
206
  * Identidade do item dentro da run — id de negócio do dev (ex.: nº da nota)
207
- * ou uuid4-hex gerado pelo SDK. Repetido na mesma run = ignorado (primeiro vence).
207
+ * ou uuid4-hex gerado pelo SDK. Repetido na mesma run = upsert (vale o último reporte).
208
208
  */
209
209
  id: string;
210
210
  /** Ordem de emissão no agente (contador monotônico por run) */
@@ -216,6 +216,8 @@ interface RunItemInput {
216
216
  payload?: Record<string, unknown>;
217
217
  /** ISO 8601 — relógio do agente, apenas para exibição */
218
218
  ts?: string;
219
+ /** Ids de anexo (print do item) — opcional; mesmo teto por entrada das linhas de log */
220
+ attachments?: string[];
219
221
  }
220
222
  /** POST /v1/tasks/runs/{id}/items — aceito mesmo com a run cancelada/terminal */
221
223
  interface RunItemsCreateRequest {
@@ -231,13 +233,13 @@ interface RunItemsCreateResponse {
231
233
  type RunAttachmentContentType = 'image/png' | 'image/jpeg' | 'image/webp';
232
234
  /** POST /v1/tasks/runs/{id}/attachments */
233
235
  interface RunAttachmentCreateRequest {
234
- /** Id do anexo gerado pelo agente (32 hex) — já referenciado na linha de log */
236
+ /** Id do anexo gerado pelo agente (32 hex) — já referenciado no log/item/step que o carrega */
235
237
  id: string;
236
238
  content_type: RunAttachmentContentType;
237
239
  /** Tamanho exato do binário; o PUT presignado é assinado com este Content-Length */
238
240
  size_bytes: number;
239
241
  caption?: string;
240
- /** ISO 8601 — ts da linha de log que referencia o anexo */
242
+ /** ISO 8601 — ts do evento (log/item/step) que referencia o anexo */
241
243
  log_ts?: string;
242
244
  width?: number;
243
245
  height?: number;
@@ -25,17 +25,18 @@ ESM e CommonJS (`const { hobots } = require('hobots')`), com `.d.ts`. Depois da
25
25
  | --- | --- | --- |
26
26
  | `runId`, `task` | `string` | Identidade da execução (`''` fora de uma execução) |
27
27
  | `params` | `Record<string, unknown>` | Params da solicitação; `{}` no modo start |
28
- | `log` | `RunLogger` — `debug/info/warn/error/success(message, ...rest)` | Nunca lança. Vários argumentos viram texto unidos por espaço (como `console.log`) |
29
- | `items` | `RunItems` — `add(item)`, `succeeded(payload?, opts?)`, `failed(message, payload?, opts?)`, `occurrence(message, payload?, opts?)`; `opts.id` = identidade de negócio. `each(list, (element, item, index) => …, { id?, payload?, op?, description?, progress? }) => Promise<{ total, succeeded, failed, occurrence }>` processa a lista inteira: exceção no handler vira item `failed` + step em erro e o loop segue; `item: RunItemHandle` = `succeeded/failed/occurrence`, `id`, `index`, `step`, `reported` | Ponto de parada cooperativo (o `each` para só em cancelamento/timeout) |
28
+ | `log` | `RunLogger` — `debug/info/warn/error/success(message, ...rest)` | Nunca lança. Vários argumentos viram texto unidos por espaço (como `console.log`); um `hobots.image(...)` no **último** argumento vira o print da linha |
29
+ | `items` | `RunItems` — `add(item)`, `succeeded(payload?, opts?)`, `failed(message, payload?, opts?)`, `occurrence(message, payload?, opts?)`; `opts.id` = identidade de negócio, `opts.image` = print do item. `each(list, (element, item, index) => …, { id?, payload?, op?, description?, progress? }) => Promise<{ total, succeeded, failed, occurrence }>` processa a lista inteira: exceção no handler vira item `failed` + step em erro e o loop segue; `item: RunItemHandle` = `succeeded(payload?, { image }?)/failed(msg, payload?, { image }?)/occurrence(…)`, `id`, `index`, `step`, `reported` | Ponto de parada cooperativo (o `each` para só em cancelamento/timeout) |
30
30
  | `progress` | `RunProgressReporter` — `total(n)`, `advance(count = 1)`, `set(current, total?)`, `current`, `snapshot` | `total/advance/set` são pontos de parada |
31
- | `transaction` | `Transaction` — `startChild(op?, description?)`, `setTag`, `setStatus`, `setName`; `Step` — `startChild`, `setStatus('ok' \| 'warning' \| 'error', message?)`, `setError(err, message?)` (motivo em `data.error`, exibido na Central), `setData(k, v)`, `finish()` | Finalizada pelo SDK; `startChild` é ponto de parada |
31
+ | `transaction` | `Transaction` — `startChild(op?, description?)`, `setTag`, `setStatus`, `setName`; `Step` — `startChild`, `setStatus('ok' \| 'warning' \| 'error', message?, { image }?)`, `setError(err, message?, { image }?)` (motivo em `data.error`, print em `data.attachments`, exibidos na Central), `setData(k, v)`, `finish()` | Finalizada pelo SDK; `startChild` é ponto de parada |
32
32
  | `signal` | `AbortSignal` | Cancelamento (app, timeout, shutdown) |
33
33
  | `throwIfCanceled()` | `() => void` | Lança `RunCanceled` se o signal disparou |
34
34
  | `sleep(ms)` | `(ms: number) => Promise<void>` | Rejeita com `RunCanceled` no cancelamento |
35
- | `attach(image, options?)` | `(image: AttachInput, options?: AttachOptions) => Promise<AttachResult>` | `AttachInput = Uint8Array \| ArrayBuffer \| string` (caminho); `AttachOptions = { caption?, level?, message?, contentType? }`; `AttachResult = { id, ok, reason? }`. Nunca lança |
36
35
  | `retry(fn, options?)` | como `hobots.retry` | |
37
36
 
38
- Fora de uma execução, `hobots.*` cai em fallbacks seguros com aviso único por membro (log ecoa no terminal; items/progress no-op; `signal` nunca aborta; `attach` resolve `{ ok: false }`).
37
+ `hobots.image(input, options?)` — `(input: AttachInput, options?: ImageOptions) => ImageAttachment`; `AttachInput = Uint8Array \| ArrayBuffer \| string` (caminho); `ImageOptions = { caption?, contentType? }`; `ImageInput = AttachInput \| ImageAttachment` é o que `log.*` (só o embrulho), `items.*` e `step.setStatus/setError` aceitam. Puro, nunca lança; o upload (segundo plano, nunca bloqueia) acontece quando o evento é registrado.
38
+
39
+ Fora de uma execução, `hobots.*` cai em fallbacks seguros com aviso único por membro (log ecoa no terminal, imagem descartada; items/progress no-op; `signal` nunca aborta).
39
40
 
40
41
  ## Tabelas de opções
41
42
 
@@ -88,7 +89,7 @@ Fora de uma execução, `hobots.*` cai em fallbacks seguros com aviso único por
88
89
  | --- | --- | --- |
89
90
  | `type` | `'fatal' \| 'error' \| 'warning' \| 'success'` | default `'error'` |
90
91
  | `tags`, `extra`, `fingerprint` | | metadados do evento (`fingerprint` sobrescreve o agrupamento) |
91
- | `attachment` | `AttachInput \| { image, caption?, contentType? }` | print anexado à linha de log da execução atual |
92
+ | `attachment` | `ImageInput \| { image, caption?, contentType? }` | print anexado à linha de log da execução atual |
92
93
 
93
94
  ### `TaskHandler`
94
95
 
package/docs/protocol.md CHANGED
@@ -117,7 +117,7 @@ Corpo `RunUpdateRequest`:
117
117
  "logs": [
118
118
  { "ts": "…", "level": "info", "message": "…" }, // delta; o servidor concatena
119
119
  { "ts": "…", "level": "info", "message": "portal após o login",
120
- "attachments": ["<32 hex>"] } // opcional; ids de anexo (máx. 4)
120
+ "attachments": ["<32 hex>"] } // opcional; ids de anexo (máx. 4) — hobots.image() no log
121
121
  ],
122
122
  "error": { "type": "Error", "message": "…", "stack": "…" }, // só com 'failed'
123
123
  "progress": { "current": 10, "total": 100 } // opcional: snapshot de hobots.progress
@@ -149,13 +149,16 @@ Corpo `RunItemsCreateRequest`:
149
149
  "status": "succeeded", // 'succeeded' | 'failed' | 'occurrence'
150
150
  "message": "…", // opcional — motivo (failed/occurrence), máx. 2100
151
151
  "payload": { "numero_nota": "12345", "cnpj_prestador": "…" }, // opcional, máx. 16 KB
152
- "ts": "2026-08-10T12:00:00.000Z" // opcional — relógio do agente, só exibição
152
+ "ts": "2026-08-10T12:00:00.000Z", // opcional — relógio do agente, só exibição
153
+ "attachments": ["<32 hex>"] // opcional — ids de anexo (máx. 4): print do item
153
154
  }
154
155
  ]
155
156
  }
156
157
  ```
157
158
 
158
- Resposta `200` `RunItemsCreateResponse`: `{ "accepted": 1, "duplicates": 0 }` — `duplicates` conta os ignorados por `(run, id)` repetido.
159
+ Resposta `200` `RunItemsCreateResponse`: `{ "accepted": 1, "duplicates": 0 }` — `duplicates` conta os colapsados por `(run, id)` repetido no mesmo lote (vale o último reporte; no banco é upsert).
160
+
161
+ - `attachments` é **opcional** e aditivo (uma API anterior ao recurso descarta o campo). As steps carregam o print do mesmo jeito, em `data.attachments` do `StepData` que vai no `POST /v1/ingest` da transaction — sem campo novo no wire.
159
162
 
160
163
  - **Run cancelada/terminal aceita itens** (200): item é fato de trabalho já feito — mesmo racional do log tardio. Esta rota não tem 409.
161
164
  - 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.
@@ -177,11 +180,11 @@ Corpo `RunAttachmentCreateRequest`:
177
180
 
178
181
  ```jsonc
179
182
  {
180
- "id": "<32 hex>", // gerado pelo agente, já referenciado na linha de log
183
+ "id": "<32 hex>", // gerado pelo agente, já referenciado no log/item/step
181
184
  "content_type": "image/png", // 'image/png' | 'image/jpeg' | 'image/webp'
182
185
  "size_bytes": 184320, // exato — entra na assinatura da URL
183
186
  "caption": "portal após o login", // opcional, máx. 500
184
- "log_ts": "…", // opcional, ISO 8601 — ts da linha de log
187
+ "log_ts": "…", // opcional, ISO 8601 — ts do evento (log/item/step)
185
188
  "width": 1920, "height": 1080 // opcionais
186
189
  }
187
190
  ```
@@ -216,7 +219,7 @@ Confirma o upload. Corpo `RunAttachmentConfirmRequest`: `{ "status": "uploaded"
216
219
 
217
220
  É **conforto de UX**, não correção: enquanto não confirmado, o app mostra a miniatura como "enviando imagem…". O agente engole falhas desta chamada.
218
221
 
219
- Limites de anexo (aplicados no SDK **e** no servidor): PNG/JPEG/WEBP, 5 MB por imagem, 20 por execução, 4 por linha de log.
222
+ Limites de anexo (aplicados no SDK **e** no servidor): PNG/JPEG/WEBP, 5 MB por imagem, 20 por execução, 4 por evento (linha de log, item ou step).
220
223
 
221
224
  ## Onde vive o claim atômico
222
225
 
package/docs/tasks.md CHANGED
@@ -40,11 +40,11 @@ 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)` | Enviado incrementalmente (PATCHes com throttle) e ecoado no terminal. Nunca lança. |
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. |
44
44
  | `hobots.items` | Ver [Itens](#itens-de-execução-hobotsitems) |
45
45
  | `hobots.progress` | Ver [Progresso](#progresso-hobotsprogress) |
46
46
  | `hobots.transaction` | Transaction da execução, já correlacionada e finalizada pelo SDK. `startChild(op, descrição)` = uma step no waterfall. |
47
- | `hobots.attach(imagem, opts?)` | Ver [Anexos](#anexos-screenshots) |
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) |
@@ -124,20 +124,39 @@ for (const nota of notas) {
124
124
 
125
125
  Opcional e independente de `items`. Sem `total()` nada é enviado. Snapshot absoluto que pega carona nos PATCHes de log (no pior caso 1/s); o PATCH terminal leva o valor final. `set(atual, total?)`, `current` e `snapshot` completam a API. `total/advance/set` são pontos de parada cooperativos; `current`/`snapshot` nunca lançam.
126
126
 
127
- ## Anexos (screenshots)
127
+ ## Imagens (screenshots)
128
+
129
+ Um print vai **junto do evento** que ele ilustra — a linha de log, o item ou a step — para o
130
+ usuário ver o estado do robô no momento em que aquilo aconteceu (botão de imagem no app):
128
131
 
129
132
  ```js
130
- await hobots.attach(await page.screenshot(), { caption: 'portal após o login' });
133
+ // na linha de log: hobots.image(...) como ÚLTIMO argumento
134
+ hobots.log.info('portal após o login', hobots.image(await page.screenshot()));
135
+ hobots.log.error('login falhou', hobots.image(await page.screenshot(), { caption: 'tela do erro' }));
136
+
137
+ // no item
138
+ hobots.items.failed('nota rejeitada', { numero }, { image: await page.screenshot() });
139
+ await hobots.items.each(notas, async (nota, item) => {
140
+ …
141
+ item.occurrence('sem fatura no mês', undefined, { image: await page.screenshot() });
142
+ });
131
143
 
144
+ // na step
145
+ const step = hobots.transaction.startChild('emitir', numero);
132
146
  try {
133
- await emitir(params);
147
+ await emitir(numero);
134
148
  } catch (erro) {
135
- hobots.captureException(erro, { attachment: { image: await page.screenshot(), caption: 'tela no momento do erro' } });
149
+ step.setError(erro, undefined, { image: await page.screenshot() });
136
150
  throw erro;
151
+ } finally {
152
+ step.finish();
137
153
  }
154
+
155
+ // numa issue (módulo Issues) — o print vai na linha de log que espelha o erro
156
+ hobots.captureException(erro, { attachment: hobots.image(await page.screenshot(), { caption: 'tela no momento do erro' }) });
138
157
  ```
139
158
 
140
- O SDK não tira o print: aceita bytes (`Uint8Array`/`ArrayBuffer`/`Buffer`) ou um caminho de arquivo. A linha de log entra na hora; 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 linha de log. Fora de uma execução não há onde pendurar a imagem (descartada com aviso).
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.
141
160
 
142
161
  ## Cancelamento cooperativo
143
162
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hobots",
3
- "version": "3.3.0",
4
- "description": "SDK JavaScript da Hobots \u2014 tudo \u00e9 uma task: register() atende solicita\u00e7\u00f5es da plataforma, start() roda sempre e reporta logs, itens e progresso. Credenciais por env, heartbeat sempre ligado, objeto global `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`.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "license": "MIT",
@@ -33,11 +33,13 @@
33
33
  "dev": "tsup --watch",
34
34
  "lint": "eslint . --max-warnings 0",
35
35
  "test": "vitest run",
36
+ "test:coverage": "vitest run --coverage",
36
37
  "test:watch": "vitest",
37
38
  "typecheck": "tsc --noEmit"
38
39
  },
39
40
  "devDependencies": {
40
41
  "@types/node": "^20",
42
+ "@vitest/coverage-v8": "^4.1.10",
41
43
  "@workspace/eslint-config": "workspace:*",
42
44
  "@workspace/typescript-config": "workspace:*",
43
45
  "@workspace/utils": "workspace:*",