hobots 1.9.2 → 2.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/protocol.ts"],"sourcesContent":["/**\n * Tipos do protocolo do Hobots (SDK → servidor).\n *\n * Este arquivo é a fonte de verdade do wire format e só pode reexportar tipos:\n * o app valida os payloads com zod e trava o drift via `satisfies z.ZodType<...>`\n * contra estes tipos (importados de `hobots/protocol`).\n */\n\n// Wire format de tasks (agente ↔ servidor) — reexportado aqui para o app\n// consumir tudo de `hobots/protocol`.\nexport type {\n RunStatus,\n RunUpdateStatus,\n RunLogLevel,\n RunLogEntry,\n AgentInfo,\n PollRequest,\n RunDescriptor,\n PollResponse,\n RunError,\n RunUpdateRequest,\n RunUpdateResponse,\n RunAttachmentContentType,\n RunAttachmentCreateRequest,\n RunAttachmentCreateResponse,\n RunAttachmentConfirmRequest,\n RunAttachmentConfirmResponse,\n} from './tasks/protocol';\n\nexport type Severity = 'fatal' | 'error' | 'warning' | 'info' | 'debug' | 'success';\n\n/** Frames em ordem innermost-first (mesma ordem impressa pelo V8). */\nexport interface StackFrame {\n function?: string;\n filename?: string;\n lineno?: number;\n colno?: number;\n in_app: boolean;\n}\n\nexport interface Breadcrumb {\n /** ISO 8601 */\n timestamp: string;\n type?: string;\n category?: string;\n message?: string;\n level?: Severity;\n data?: Record<string, unknown>;\n}\n\nexport interface ExceptionData {\n type: string;\n value: string;\n stacktrace?: { frames: StackFrame[] };\n}\n\nexport interface EventUser {\n id?: string;\n username?: string;\n email?: string;\n}\n\nexport interface EventPayload {\n /** uuid v4 sem hífens (32 hex), gerado pelo SDK */\n event_id: string;\n /** ISO 8601 */\n timestamp: string;\n level: Severity;\n message?: string;\n exception?: ExceptionData;\n /** Override de agrupamento; quando ausente o servidor calcula pelo stack trace */\n fingerprint?: string[];\n breadcrumbs?: Breadcrumb[];\n tags?: Record<string, string>;\n user?: EventUser;\n contexts?: Record<string, Record<string, unknown>>;\n extra?: Record<string, unknown>;\n release?: string;\n environment?: string;\n}\n\n/** Status de uma step (etapa). `warning` = terminou, mas merece atenção (não é falha). */\nexport type StepStatus = 'ok' | 'warning' | 'error' | 'cancelled';\n\nexport interface StepData {\n /** 16 hex */\n step_id: string;\n parent_step_id?: string;\n op?: string;\n description?: string;\n status?: StepStatus;\n /** ISO 8601 */\n start_timestamp: string;\n /** ISO 8601 (fim da step) */\n timestamp: string;\n data?: Record<string, unknown>;\n}\n\nexport interface TransactionPayload {\n event_id: string;\n name: string;\n op?: string;\n status: string;\n start_timestamp: string;\n timestamp: string;\n steps: StepData[];\n tags?: Record<string, string>;\n release?: string;\n environment?: string;\n /** Correlaciona esta transaction a uma execução de task (ctx.tx dentro de um task handler) */\n run_id?: string;\n}\n\nexport interface HeartbeatPayload {\n instance_id: string;\n name?: string;\n interval_ms: number;\n /** ISO 8601 */\n timestamp: string;\n metadata?: Record<string, unknown>;\n release?: string;\n}\n\nexport type EnvelopeItem =\n | { type: 'event'; payload: EventPayload }\n | { type: 'transaction'; payload: TransactionPayload }\n | { type: 'heartbeat'; payload: HeartbeatPayload };\n\nexport interface Envelope {\n sdk: { name: string; version: string };\n /** ISO 8601 */\n sent_at: string;\n items: EnvelopeItem[];\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
1
+ {"version":3,"sources":["../src/protocol.ts"],"sourcesContent":["/**\n * Tipos do protocolo do Hobots (SDK → servidor).\n *\n * Este arquivo é a fonte de verdade do wire format e só pode reexportar tipos:\n * o app valida os payloads com zod e trava o drift via `satisfies z.ZodType<...>`\n * contra estes tipos (importados de `hobots/protocol`).\n */\n\n// Wire format de tasks (agente ↔ servidor) — reexportado aqui para o app\n// consumir tudo de `hobots/protocol`.\nexport type {\n RunStatus,\n RunUpdateStatus,\n RunLogLevel,\n RunLogEntry,\n InstanceInfo,\n PollRequest,\n RunDescriptor,\n PollResponse,\n RunError,\n RunUpdateRequest,\n RunUpdateResponse,\n RunItemStatus,\n RunItemInput,\n RunItemsCreateRequest,\n RunItemsCreateResponse,\n RunAttachmentContentType,\n RunAttachmentCreateRequest,\n RunAttachmentCreateResponse,\n RunAttachmentConfirmRequest,\n RunAttachmentConfirmResponse,\n} from './tasks/protocol';\n\nexport type Severity = 'fatal' | 'error' | 'warning' | 'info' | 'debug' | 'success';\n\n/** Frames em ordem innermost-first (mesma ordem impressa pelo V8). */\nexport interface StackFrame {\n function?: string;\n filename?: string;\n lineno?: number;\n colno?: number;\n in_app: boolean;\n}\n\nexport interface Breadcrumb {\n /** ISO 8601 */\n timestamp: string;\n type?: string;\n category?: string;\n message?: string;\n level?: Severity;\n data?: Record<string, unknown>;\n}\n\nexport interface ExceptionData {\n type: string;\n value: string;\n stacktrace?: { frames: StackFrame[] };\n}\n\nexport interface EventUser {\n id?: string;\n username?: string;\n email?: string;\n}\n\nexport interface EventPayload {\n /** uuid v4 sem hífens (32 hex), gerado pelo SDK */\n event_id: string;\n /** ISO 8601 */\n timestamp: string;\n level: Severity;\n message?: string;\n exception?: ExceptionData;\n /** Override de agrupamento; quando ausente o servidor calcula pelo stack trace */\n fingerprint?: string[];\n breadcrumbs?: Breadcrumb[];\n tags?: Record<string, string>;\n user?: EventUser;\n contexts?: Record<string, Record<string, unknown>>;\n extra?: Record<string, unknown>;\n release?: string;\n environment?: string;\n}\n\n/** Status de uma step (etapa). `warning` = terminou, mas merece atenção (não é falha). */\nexport type StepStatus = 'ok' | 'warning' | 'error' | 'cancelled';\n\nexport interface StepData {\n /** 16 hex */\n step_id: string;\n parent_step_id?: string;\n op?: string;\n description?: string;\n status?: StepStatus;\n /** ISO 8601 */\n start_timestamp: string;\n /** ISO 8601 (fim da step) */\n timestamp: string;\n data?: Record<string, unknown>;\n}\n\nexport interface TransactionPayload {\n event_id: string;\n name: string;\n op?: string;\n status: string;\n start_timestamp: string;\n timestamp: string;\n steps: StepData[];\n tags?: Record<string, string>;\n release?: string;\n environment?: string;\n /** Correlaciona esta transaction a uma execução de task (ctx.tx dentro de um task handler) */\n run_id?: string;\n}\n\nexport interface HeartbeatPayload {\n instance_id: string;\n name?: string;\n interval_ms: number;\n /** ISO 8601 */\n timestamp: string;\n metadata?: Record<string, unknown>;\n release?: string;\n}\n\nexport type EnvelopeItem =\n | { type: 'event'; payload: EventPayload }\n | { type: 'transaction'; payload: TransactionPayload }\n | { type: 'heartbeat'; payload: HeartbeatPayload };\n\nexport interface Envelope {\n sdk: { name: string; version: string };\n /** ISO 8601 */\n sent_at: string;\n items: EnvelopeItem[];\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
@@ -22,7 +22,7 @@ interface RunLogEntry {
22
22
  */
23
23
  attachments?: string[];
24
24
  }
25
- interface AgentInfo {
25
+ interface InstanceInfo {
26
26
  /** Identidade canônica do agente — correlaciona com o heartbeat de telemetria */
27
27
  instance_id: string;
28
28
  name?: string;
@@ -32,10 +32,15 @@ interface AgentInfo {
32
32
  host?: string;
33
33
  /** Tasks registradas no agente */
34
34
  tasks?: string[];
35
+ /**
36
+ * Intervalo de poll em ms — alimenta a tolerância de offline do servidor (3×) em
37
+ * agentes poll-only, que sem isso herdariam a tolerância do heartbeat (30s × 3).
38
+ */
39
+ poll_interval_ms?: number;
35
40
  }
36
41
  /** POST /v1/tasks/poll */
37
42
  interface PollRequest {
38
- agent?: AgentInfo;
43
+ instance?: InstanceInfo;
39
44
  /** Quantas runs reivindicar de uma vez (1..5). Default: 1 */
40
45
  max?: number;
41
46
  /** Só entrega runs destas tasks (nomes registrados no agente) */
@@ -74,6 +79,39 @@ interface RunUpdateRequest {
74
79
  interface RunUpdateResponse {
75
80
  ok: true;
76
81
  }
82
+ /**
83
+ * Status de negócio de um item processado pela run:
84
+ * - 'succeeded': processado com sucesso
85
+ * - 'failed': falha técnica ao processar o item
86
+ * - 'occurrence': ocorrência de negócio (ex.: "nota nº 10 não encontrada")
87
+ */
88
+ type RunItemStatus = 'succeeded' | 'failed' | 'occurrence';
89
+ interface RunItemInput {
90
+ /**
91
+ * Identidade do item dentro da run — id de negócio do dev (ex.: nº da nota)
92
+ * ou uuid4-hex gerado pelo SDK. Repetido na mesma run = ignorado (primeiro vence).
93
+ */
94
+ id: string;
95
+ /** Ordem de emissão no agente (contador monotônico por run) */
96
+ seq: number;
97
+ status: RunItemStatus;
98
+ /** Motivo — esperado em 'failed'/'occurrence' */
99
+ message?: string;
100
+ /** Atributos definidos pelo dev; chaves configuráveis como colunas na plataforma */
101
+ payload?: Record<string, unknown>;
102
+ /** ISO 8601 — relógio do agente, apenas para exibição */
103
+ ts?: string;
104
+ }
105
+ /** POST /v1/tasks/runs/{id}/items — aceito mesmo com a run cancelada/terminal */
106
+ interface RunItemsCreateRequest {
107
+ items: RunItemInput[];
108
+ }
109
+ interface RunItemsCreateResponse {
110
+ /** Itens inseridos */
111
+ accepted: number;
112
+ /** Ignorados por id repetido na run (retry de rede é idempotente) */
113
+ duplicates: number;
114
+ }
77
115
  /** Formatos aceitos para anexo. O S3 valida o content-type contra a assinatura. */
78
116
  type RunAttachmentContentType = 'image/png' | 'image/jpeg' | 'image/webp';
79
117
  /** POST /v1/tasks/runs/{id}/attachments */
@@ -223,4 +261,4 @@ interface Envelope {
223
261
  items: EnvelopeItem[];
224
262
  }
225
263
 
226
- export type { AgentInfo, Breadcrumb, Envelope, EnvelopeItem, EventPayload, EventUser, ExceptionData, HeartbeatPayload, PollRequest, PollResponse, RunAttachmentConfirmRequest, RunAttachmentConfirmResponse, RunAttachmentContentType, RunAttachmentCreateRequest, RunAttachmentCreateResponse, RunDescriptor, RunError, RunLogEntry, RunLogLevel, RunStatus, RunUpdateRequest, RunUpdateResponse, RunUpdateStatus, Severity, StackFrame, StepData, StepStatus, TransactionPayload };
264
+ export type { Breadcrumb, Envelope, EnvelopeItem, EventPayload, EventUser, ExceptionData, HeartbeatPayload, InstanceInfo, PollRequest, PollResponse, RunAttachmentConfirmRequest, RunAttachmentConfirmResponse, RunAttachmentContentType, RunAttachmentCreateRequest, RunAttachmentCreateResponse, RunDescriptor, RunError, RunItemInput, RunItemStatus, RunItemsCreateRequest, RunItemsCreateResponse, RunLogEntry, RunLogLevel, RunStatus, RunUpdateRequest, RunUpdateResponse, RunUpdateStatus, Severity, StackFrame, StepData, StepStatus, TransactionPayload };
@@ -22,7 +22,7 @@ interface RunLogEntry {
22
22
  */
23
23
  attachments?: string[];
24
24
  }
25
- interface AgentInfo {
25
+ interface InstanceInfo {
26
26
  /** Identidade canônica do agente — correlaciona com o heartbeat de telemetria */
27
27
  instance_id: string;
28
28
  name?: string;
@@ -32,10 +32,15 @@ interface AgentInfo {
32
32
  host?: string;
33
33
  /** Tasks registradas no agente */
34
34
  tasks?: string[];
35
+ /**
36
+ * Intervalo de poll em ms — alimenta a tolerância de offline do servidor (3×) em
37
+ * agentes poll-only, que sem isso herdariam a tolerância do heartbeat (30s × 3).
38
+ */
39
+ poll_interval_ms?: number;
35
40
  }
36
41
  /** POST /v1/tasks/poll */
37
42
  interface PollRequest {
38
- agent?: AgentInfo;
43
+ instance?: InstanceInfo;
39
44
  /** Quantas runs reivindicar de uma vez (1..5). Default: 1 */
40
45
  max?: number;
41
46
  /** Só entrega runs destas tasks (nomes registrados no agente) */
@@ -74,6 +79,39 @@ interface RunUpdateRequest {
74
79
  interface RunUpdateResponse {
75
80
  ok: true;
76
81
  }
82
+ /**
83
+ * Status de negócio de um item processado pela run:
84
+ * - 'succeeded': processado com sucesso
85
+ * - 'failed': falha técnica ao processar o item
86
+ * - 'occurrence': ocorrência de negócio (ex.: "nota nº 10 não encontrada")
87
+ */
88
+ type RunItemStatus = 'succeeded' | 'failed' | 'occurrence';
89
+ interface RunItemInput {
90
+ /**
91
+ * Identidade do item dentro da run — id de negócio do dev (ex.: nº da nota)
92
+ * ou uuid4-hex gerado pelo SDK. Repetido na mesma run = ignorado (primeiro vence).
93
+ */
94
+ id: string;
95
+ /** Ordem de emissão no agente (contador monotônico por run) */
96
+ seq: number;
97
+ status: RunItemStatus;
98
+ /** Motivo — esperado em 'failed'/'occurrence' */
99
+ message?: string;
100
+ /** Atributos definidos pelo dev; chaves configuráveis como colunas na plataforma */
101
+ payload?: Record<string, unknown>;
102
+ /** ISO 8601 — relógio do agente, apenas para exibição */
103
+ ts?: string;
104
+ }
105
+ /** POST /v1/tasks/runs/{id}/items — aceito mesmo com a run cancelada/terminal */
106
+ interface RunItemsCreateRequest {
107
+ items: RunItemInput[];
108
+ }
109
+ interface RunItemsCreateResponse {
110
+ /** Itens inseridos */
111
+ accepted: number;
112
+ /** Ignorados por id repetido na run (retry de rede é idempotente) */
113
+ duplicates: number;
114
+ }
77
115
  /** Formatos aceitos para anexo. O S3 valida o content-type contra a assinatura. */
78
116
  type RunAttachmentContentType = 'image/png' | 'image/jpeg' | 'image/webp';
79
117
  /** POST /v1/tasks/runs/{id}/attachments */
@@ -223,4 +261,4 @@ interface Envelope {
223
261
  items: EnvelopeItem[];
224
262
  }
225
263
 
226
- export type { AgentInfo, Breadcrumb, Envelope, EnvelopeItem, EventPayload, EventUser, ExceptionData, HeartbeatPayload, PollRequest, PollResponse, RunAttachmentConfirmRequest, RunAttachmentConfirmResponse, RunAttachmentContentType, RunAttachmentCreateRequest, RunAttachmentCreateResponse, RunDescriptor, RunError, RunLogEntry, RunLogLevel, RunStatus, RunUpdateRequest, RunUpdateResponse, RunUpdateStatus, Severity, StackFrame, StepData, StepStatus, TransactionPayload };
264
+ export type { Breadcrumb, Envelope, EnvelopeItem, EventPayload, EventUser, ExceptionData, HeartbeatPayload, InstanceInfo, PollRequest, PollResponse, RunAttachmentConfirmRequest, RunAttachmentConfirmResponse, RunAttachmentContentType, RunAttachmentCreateRequest, RunAttachmentCreateResponse, RunDescriptor, RunError, RunItemInput, RunItemStatus, RunItemsCreateRequest, RunItemsCreateResponse, RunLogEntry, RunLogLevel, RunStatus, RunUpdateRequest, RunUpdateResponse, RunUpdateStatus, Severity, StackFrame, StepData, StepStatus, TransactionPayload };
@@ -84,7 +84,7 @@ Habilitadas com `tasks: true` (ou `TasksOptions`) no `init()` único. Todas expo
84
84
  - **`start()`** — começa o polling. **Mantém o processo vivo** até `stop()`/`close()` — o timer não é `unref`'d, ao contrário dos da telemetria. Lança se o `init()` não configurou `tasks` ou se nenhuma task foi registrada. Também instala os handlers de saída (SIGINT/SIGTERM/erro fatal) que reportam as runs ativas como `AgentTerminated`, a menos que `installExitHandlers: false`.
85
85
  - **`stop(timeoutMs = 30_000)`** → `Promise<boolean>` — encerra o polling e aguarda as [runs](./concepts.md#run) ativas terminarem. Para **só as tasks**: a telemetria (heartbeat, captura de erros) continua funcionando. Devolve `false` se estourou o timeout — as runs que ainda estavam rodando são reportadas como `AgentTerminated`. Para encerrar tudo, use [`close()`](./concepts.md#fila-flush-e-close).
86
86
 
87
- No poll, o agente se identifica com `AgentInfo` (`hobots/protocol`): o campo `instance_id` (o `instanceId` do `init()`) é a identidade canônica do agente — a mesma do heartbeat de telemetria, então os dois canais convergem em um agente no painel.
87
+ No poll, o agente se identifica com `InstanceInfo` (`hobots/protocol`): o campo `instance_id` (o `instanceId` do `init()`) é a identidade canônica do agente — a mesma do heartbeat de telemetria, então os dois canais convergem em um agente no painel.
88
88
 
89
89
  ---
90
90
 
@@ -168,15 +168,24 @@ interface RunContext {
168
168
  runId: string;
169
169
  task: string;
170
170
  log: RunLogger; // ctx.log.debug/info/warn/error/success(message)
171
+ items: RunItems; // ctx.items.add/succeeded/failed/occurrence — a entrega do bot
171
172
  tx: Transaction; // transaction desta run — correlacionada e finalizada pelo SDK
172
173
  signal: AbortSignal;
173
174
  throwIfCanceled(): void; // lança RunCanceled se o signal já disparou
174
175
  sleep(ms: number): Promise<void>; // espera cancelável — rejeita com RunCanceled no abort
175
176
  }
177
+
178
+ interface RunItems {
179
+ add(item: { status: RunItemStatus; id?: string; message?: string; payload?: Record<string, unknown> }): void;
180
+ succeeded(payload?: Record<string, unknown>, options?: { id?: string }): void;
181
+ failed(message: string, payload?: Record<string, unknown>, options?: { id?: string }): void;
182
+ occurrence(message: string, payload?: Record<string, unknown>, options?: { id?: string }): void;
183
+ }
176
184
  ```
177
185
 
178
186
  - **`runId` / `task`** — identificam a execução; úteis para correlacionar com os seus próprios logs.
179
187
  - **`log.<level>(message)`** — níveis `debug`, `info`, `warn`, `error` e `success`; enfileira uma linha de log enviada ao app em PATCHes incrementais, e **também imprime no terminal local**. Conceito: [Run](./concepts.md#run).
188
+ - **`items.<status>(…)`** — registra um item processado/gerado pela execução (síncrono, bufferizado, lotes de até 500). `id` opcional = idempotência dentro da run. Guia: [Itens de execução](./tasks.md#itens-de-execução-ctxitems).
180
189
  - **`tx`** — a [transaction de performance da run](./concepts.md#transaction-de-run-ctxtx). Use `ctx.tx.startChild(op, description)` para medir as etapas; **não** chame `finish()` nela. `startTransaction()` dentro do handler devolve essa mesma transaction.
181
190
  - **`signal`** — `AbortSignal` que dispara em cancelamento no app, timeout ou shutdown.
182
191
  - **`throwIfCanceled()`** — o ponto de parada de uma linha: lança `RunCanceled` se o `signal` já disparou.
@@ -209,6 +218,8 @@ Todos os prazos do SDK num lugar só. A coluna **Onde configura** diz se o valor
209
218
  | --- | --- | --- | --- |
210
219
  | poll sem trabalho | `5000` ms (mín. `1000`) | `tasks: { pollIntervalMs }` | é só o **teto da espera quando não há nada**: com trabalho na fila o próximo poll é imediato ([drain mode](./concepts.md#drain-mode)) |
211
220
  | flush de logs da run | `2000` ms | `tasks: { logFlushIntervalMs }` | ou na hora, ao acumular 20 linhas |
221
+ | flush de itens da run | `2000` ms | — (acompanha `logFlushIntervalMs`) | ou na hora, ao acumular 100 itens; POSTs de até 500 |
222
+ | espera pelos itens no fim da run | `15000` ms | — (fixo) | antes do PATCH terminal, drena o buffer de itens — os totalizadores ficam completos quando a run termina |
212
223
  | validade da URL de upload de anexo | `900` s | — (servidor) | tempo para o PUT no S3 depois que a API presigna |
213
224
  | espera pelos anexos no fim da run | `15000` ms | — (fixo) | antes do PATCH terminal, aguarda os uploads pendentes; no estouro a run termina e o app mostra "imagem não enviada" |
214
225
  | uploads de anexo simultâneos | `3` | — (fixo) | por execução; os demais ficam na fila |
package/docs/concepts.md CHANGED
@@ -41,9 +41,9 @@ Cada API do SDK alimenta um módulo do app da Hobots (app.hobots.app). O mapa:
41
41
  | No SDK | No app |
42
42
  | --- | --- |
43
43
  | **Agente** — dono do Client Secret (`hb_…`) que autentica tudo | **Configurações → Agentes** (é lá que você copia a credencial) |
44
- | **Instância** — o par `(project_id, instance_id)`; heartbeat e poll de tasks convergem nele | Módulo **Agentes** (uptime, performance) |
44
+ | **Instância** — o par `(agent_id, instance_id)`; heartbeat e poll de tasks convergem nele | Módulo **Instâncias** (uptime, performance) |
45
45
  | **[Evento](#evento)** agrupado em **issue** — `captureException` / `captureMessage` | Módulo **Issues** |
46
- | **[Transaction](#transaction) / etapas** — `startTransaction` (e [`ctx.tx`](#transaction-de-run-ctxtx) dentro de uma run) | Performance no módulo **Agentes**; nas execuções, a aba de performance da solicitação |
46
+ | **[Transaction](#transaction) / etapas** — `startTransaction` (e [`ctx.tx`](#transaction-de-run-ctxtx) dentro de uma run) | Performance no módulo **Instâncias**; nas execuções, a aba de performance da solicitação |
47
47
  | **Task / [run](#run)** — `register('task-slug', …)` casa com o `task_slug` do formulário | Módulo **Solicitações** (formulários em **Configurações → Formulários**) |
48
48
 
49
49
  ---
package/docs/protocol.md CHANGED
@@ -54,8 +54,8 @@ Corpo `PollRequest`:
54
54
 
55
55
  ```jsonc
56
56
  {
57
- "agent": {
58
- "instance_id": "vm-01", // identidade canônica do agente (obrigatória)
57
+ "instance": {
58
+ "instance_id": "vm-01", // identidade canônica da instância (obrigatória)
59
59
  "name": "VM 01",
60
60
  "version": "2.0.0",
61
61
  "host": "vm-01",
@@ -68,7 +68,7 @@ Corpo `PollRequest`:
68
68
 
69
69
  Resposta `PollResponse` = `{ runs: RunDescriptor[] }`, cada `{ id, task, params, form, source, created_at }`.
70
70
 
71
- O `agent.instance_id` é a **mesma identidade** do heartbeat de telemetria (`instance_id`): o servidor correlaciona os dois canais numa tabela `agents` por `(project_id, instance_id)`. Com o `init()` único, os dois derivam do `instanceId` — o robô aparece como um agente só no painel automaticamente. O próprio poll marca a presença do agente (`last_poll_at`), por isso o heartbeat é opcional quando as tasks estão habilitadas.
71
+ 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)`. Com o `init()` único, os dois derivam do `instanceId` — o robô aparece como um agente só no painel automaticamente. O próprio poll marca a presença da instância (`last_poll_at`), por isso o heartbeat é opcional quando as tasks estão habilitadas.
72
72
 
73
73
  **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]`.
74
74
 
@@ -97,6 +97,41 @@ Corpo `RunUpdateRequest`:
97
97
 
98
98
  Ciclo de vida completo da run no servidor (`RunStatus`): `pending → claimed → running → succeeded | failed | canceled`. O agente só transiciona para `running`/`succeeded`/`failed` (`RunUpdateStatus`).
99
99
 
100
+ ### `POST /v1/tasks/runs/{id}/items`
101
+
102
+ Registra em lote os **itens de execução** (a entrega do bot — `ctx.items` no SDK). Insert-only: cada item chega com o status final; um `id` repetido na mesma run é ignorado (retry de rede idempotente).
103
+
104
+ Corpo `RunItemsCreateRequest`:
105
+
106
+ ```jsonc
107
+ {
108
+ "items": [ // 1..500 por request
109
+ {
110
+ "id": "12345", // identidade do item na run (dev) ou uuid do SDK
111
+ "seq": 0, // ordem de emissão no agente
112
+ "status": "succeeded", // 'succeeded' | 'failed' | 'occurrence'
113
+ "message": "…", // opcional — motivo (failed/occurrence), máx. 2100
114
+ "payload": { "numero_nota": "12345", "cnpj_prestador": "…" }, // opcional, máx. 16 KB
115
+ "ts": "2026-08-10T12:00:00.000Z" // opcional — relógio do agente, só exibição
116
+ }
117
+ ]
118
+ }
119
+ ```
120
+
121
+ Resposta `200` `RunItemsCreateResponse`: `{ "accepted": 1, "duplicates": 0 }` — `duplicates` conta os ignorados por `(run, id)` repetido.
122
+
123
+ - **Run cancelada/terminal aceita itens** (200): item é fato de trabalho já feito — mesmo racional do log tardio. Esta rota não tem 409.
124
+ - 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.
125
+ - 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).
126
+
127
+ | Status | Quando |
128
+ | --- | --- |
129
+ | `401` | chave do agente inválida ou inativa |
130
+ | `404` | execução inexistente ou de outro projeto |
131
+ | `413` | corpo acima de 1 MB |
132
+ | `422` | payload inválido |
133
+ | `429` | limite de 10.000 itens por execução atingido (lote inteiro rejeitado) |
134
+
100
135
  ### `POST /v1/tasks/runs/{id}/attachments`
101
136
 
102
137
  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.
package/docs/tasks.md CHANGED
@@ -48,6 +48,7 @@ interface RunContext {
48
48
  runId: string;
49
49
  task: string;
50
50
  log: RunLogger; // ctx.log.debug/info/warn/error/success(message)
51
+ items: RunItems; // ctx.items.succeeded/failed/occurrence — a entrega do bot
51
52
  tx: Transaction;
52
53
  signal: AbortSignal;
53
54
  throwIfCanceled(): void;
@@ -57,6 +58,7 @@ interface RunContext {
57
58
 
58
59
  - **`params`** — os dados do formulário configurado no app (o JSON validado).
59
60
  - **`ctx.log.<level>(message)`** — níveis `debug`, `info`, `warn`, `error` e `success`; logs enviados **incrementalmente** ao app (PATCHes com throttle), visíveis na solicitação em tempo quase real. Cada linha também é **impressa no terminal local** como `[hobots:<task>] INFO mensagem`, colorida por nível — útil para acompanhar o robô rodando na VM. A cor é desligada automaticamente fora de um TTY e quando a env `NO_COLOR` está definida; o eco local é independente do envio ao servidor.
61
+ - **`ctx.items`** — registro dos **itens que a execução processou/gerou** (notas fiscais baixadas, boletos emitidos…), com status e atributos. Aparecem na aba "itens" da solicitação, com totalizadores (veja a seção abaixo).
60
62
  - **`ctx.tx`** — a [transaction de performance](./concepts.md#transaction-de-run-ctxtx) desta run, já correlacionada e finalizada pelo SDK. Use `ctx.tx.startChild()` para medir as etapas (veja abaixo).
61
63
  - **`ctx.signal`** — `AbortSignal` que dispara quando a run é **cancelada no app**, em **timeout** ou no **shutdown**. Cancelamento é cooperativo (veja abaixo).
62
64
  - **`ctx.throwIfCanceled()`** — lança `RunCanceled` se o `signal` já disparou. O ponto de parada de 1 linha para chamar entre as etapas do trabalho.
@@ -68,6 +70,66 @@ Você pode tipar os params: `Hobots.register<{ lote: number }>(...)`.
68
70
 
69
71
  > **Dados sensíveis:** o que você escreve em `ctx.log` e as mensagens/stack de erros lançados **são enviados ao servidor** e ficam visíveis na execução no app. Não inclua senhas, tokens, documentos, dados pessoais ou qualquer informação sensível em logs e mensagens de erro.
70
72
 
73
+ ## Itens de execução (`ctx.items`)
74
+
75
+ Logs contam **como** o bot trabalhou; itens contam **o que** ele entregou. Cada item é uma unidade de negócio processada pela execução — uma nota fiscal baixada, um boleto emitido — registrada com um dos três status:
76
+
77
+ - **`succeeded`** — processado com sucesso.
78
+ - **`failed`** — falha técnica ao processar o item (ex.: o download quebrou).
79
+ - **`occurrence`** — ocorrência de negócio, que não é falha do bot (ex.: "nota nº 10 não encontrada", "CNPJ do prestador incorreto").
80
+
81
+ ```js
82
+ Hobots.register('baixar-notas', async (params, ctx) => {
83
+ for (const nota of await listarNotas(params)) {
84
+ try {
85
+ if (!(await existeNoPortal(nota))) {
86
+ ctx.items.occurrence(`nota nº ${nota.numero} não encontrada`, { numero_nota: nota.numero }, { id: nota.numero });
87
+ continue;
88
+ }
89
+ await baixar(nota);
90
+ ctx.items.succeeded({ numero_nota: nota.numero, cnpj_prestador: nota.cnpj }, { id: nota.numero });
91
+ } catch (erro) {
92
+ ctx.items.failed(String(erro), { numero_nota: nota.numero }, { id: nota.numero });
93
+ }
94
+ }
95
+ });
96
+ ```
97
+
98
+ Na plataforma, os itens aparecem na aba **itens** da solicitação, com totalizadores (total, sucesso, falhas, ocorrências), filtro por status e paginação. As chaves do `payload` que viram **colunas** da tabela são configuradas por formulário em Configurações → Formulários → itens da execução — o `payload` aceita qualquer atributo que você julgar útil (só as chaves configuradas são exibidas; o restante fica no registro).
99
+
100
+ ### API
101
+
102
+ ```ts
103
+ interface RunItems {
104
+ add(item: { status: 'succeeded' | 'failed' | 'occurrence'; id?: string; message?: string; payload?: Record<string, unknown> }): void;
105
+ succeeded(payload?, options?: { id?: string }): void;
106
+ failed(message, payload?, options?: { id?: string }): void;
107
+ occurrence(message, payload?, options?: { id?: string }): void;
108
+ }
109
+ ```
110
+
111
+ - **`id`** (opcional) — identidade de negócio do item **dentro da run** (ex.: nº da nota). Registrado uma vez: um `id` repetido na mesma run é **ignorado pelo servidor** (primeiro vence), o que torna o retry de rede idempotente. Sem `id`, o SDK gera um único e cada chamada vira um item novo.
112
+ - **`message`** — o motivo, esperado em `failed`/`occurrence` (cap 2.000 caracteres). Passa por `scrubSecrets`, como os logs.
113
+ - **`payload`** — atributos livres do item (cap 16 KB serializado; acima disso o payload é descartado com aviso e o item mantido). Strings passam por `scrubSecrets`.
114
+ - **Registro único** — o item chega com o status final; não existe "atualizar item". Se o bot reprocessa, use outra run (retry cria uma run nova, com itens próprios).
115
+
116
+ ### Como funciona (e por que não trava a run)
117
+
118
+ As chamadas são **síncronas** (nunca esperam rede): os itens entram num buffer próprio, separado da fila de status/logs, e sobem em lotes (flush a cada 2s ou 100 itens, POSTs de até 500). Antes do reporte final (`succeeded`/`failed`), o SDK **drena o buffer** (teto de 15s) — os totalizadores da solicitação ficam completos quando ela termina.
119
+
120
+ Diferente de logs e status, **uma run cancelada continua aceitando itens**: item é fato de trabalho já feito, e o handler pode continuar rodando após o cancelamento cooperativo.
121
+
122
+ ### Limites
123
+
124
+ | Limite | Valor |
125
+ | --- | --- |
126
+ | Itens por execução | **10.000** (acima disso o SDK descarta com um aviso) |
127
+ | Itens por request | 500 (o SDK fatia sozinho) |
128
+ | `payload` por item | 16 KB serializado |
129
+ | `message` | 2.000 caracteres |
130
+
131
+ > **Dados sensíveis:** como os logs, `message` e `payload` **são enviados ao servidor** e ficam visíveis na solicitação. Não inclua senhas, tokens ou dados pessoais desnecessários.
132
+
71
133
  ## Anexos (screenshots)
72
134
 
73
135
  Texto de log conta o que o robô fez; um print conta **como estava a tela**. Os métodos de captura — `Hobots.captureException(error, hint)` e `Hobots.captureMessage(message, level, hint)` — aceitam um **`attachment`** no hint. Dentro de uma execução de task, a imagem vira anexo da linha de log da run que espelha a captura, e aparece como miniatura logo abaixo dela na aba "logs do agente" da execução — clicável para ampliar.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hobots",
3
- "version": "1.9.2",
3
+ "version": "2.0.0",
4
4
  "description": "SDK JavaScript da Hobots — telemetria (erros, transactions e heartbeats) e execução remota de tarefas sob demanda (tasks) para RPAs e integrações. Um único import e um único init().",
5
5
  "type": "module",
6
6
  "sideEffects": false,