hobots 2.3.0 → 3.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.
@@ -4,247 +4,108 @@
4
4
 
5
5
  | Import | Conteúdo |
6
6
  | --- | --- |
7
- | `hobots` | Tudo: `init` único, funções de telemetria, `register`/`start`/`stop` de tasks, `close`, classes (`Client`, `Scope`, `Transaction`, `Step`), `DEFAULT_BASE_URL`, `scrubSecrets` e tipos. |
7
+ | `hobots` | `hobots` (default e named), `register`, `start`, `close`, `captureException`, `retry` e os tipos. |
8
8
  | `hobots/protocol` | Tipos do wire format (telemetria e tasks). |
9
9
 
10
- ---
10
+ ESM e CommonJS (`const { hobots } = require('hobots')`), com `.d.ts`. Depois da primeira task, `globalThis.hobots` também existe (tipado via `declare global`).
11
11
 
12
- ## Telemetria — funções
12
+ ## Funções do processo
13
13
 
14
- | Função | Assinatura | Retorno |
14
+ | Função | Assinatura | Notas |
15
15
  | --- | --- | --- |
16
- | `init` | `init(options: InitOptions): void` | — |
17
- | `getClient` | `getClient(): Client \| undefined` | cliente atual |
18
- | `captureException` | `captureException(error: unknown, hint?: CaptureHint)` | `string \| undefined` (event_id — **não** é confirmação de envio) |
19
- | `captureMessage` | `captureMessage(message: string, type?: EventType, hint?: CaptureHint)` | `string \| undefined` (idem) |
20
- | `addBreadcrumb` | `addBreadcrumb(crumb: BreadcrumbInput): void` | — |
21
- | `setTag` / `setTags` | `setTag(key, value)` · `setTags(tags)` | — |
22
- | `setUser` | `setUser(user: EventUser \| null): void` | — |
23
- | `setContext` | `setContext(name: string, ctx: Record<string,unknown> \| null): void` | — |
24
- | `setExtra` | `setExtra(key: string, value: unknown): void` | — |
25
- | `withScope` | `withScope<T>(cb: (scope: Scope) => T): T` | retorno do callback |
26
- | `startTransaction` | `startTransaction(name: string, op?: string): Transaction` | transaction (dentro de um handler de task, a própria `ctx.tx`) |
27
- | `flush` | `flush(timeoutMs = 5_000): Promise<boolean>` | enviou tudo dentro do prazo? |
28
- | `close` | `close(timeoutMs = 5_000): Promise<boolean>` | para o polling de tasks (aguarda runs) + heartbeat + flush final do transport |
29
-
30
- ### O que cada função faz
31
-
32
- Os conceitos por trás dessas chamadas estão em [Conceitos](./concepts.md); aqui está o que cada uma **faz** e o que **devolve**.
33
-
34
- - **`init(options)`** — cria o cliente, instala os handlers globais de erro (a menos que `captureUnhandled: false`), inicia o heartbeat se configurado e cria o agente de tasks se `tasks` foi passado. Chamar de novo **substitui** o cliente e o agente (os handlers registrados com `register` são preservados; o [scope](./concepts.md#scope) **não** é limpo). Chame uma vez, o mais cedo possível.
35
- - **`getClient()`** — devolve o cliente atual ou `undefined` se o `init()` nunca rodou. Útil para checar se o SDK está ativo. A config resolvida (URL base e endpoints) é acessível via `getClient()?.config` (veja abaixo).
36
- - **`captureException(error, hint?)`** — monta um [evento](./concepts.md#evento) a partir da exceção (tipo, mensagem, stack trace), mescla o scope e enfileira. O `type` é `error` a menos que você passe outro no `hint`.
37
- - **`captureMessage(message, type = 'warning', hint?)`** — mesma coisa, sem exceção: um evento só com texto. Bom para marcos (`'success'`) e avisos que não são erro.
38
- > **Atenção:** as duas devolvem um `event_id` **mesmo quando o evento foi descartado** por [amostragem](./concepts.md#amostragem-samplerate-e-tracessamplerate) ou por [`beforeSend`](./concepts.md#beforesend). O retorno não prova que algo foi enviado — só é `undefined` quando o `init()` não foi chamado.
39
- - **`addBreadcrumb(crumb)`** — adiciona uma migalha à trilha do [scope](./concepts.md#scope) global. Todos os campos são opcionais menos o `timestamp`, que o SDK preenche. A lista **não** é limpa ao enviar um evento; ela só descarta as mais antigas ao estourar `maxBreadcrumbs`. Conceito: [Breadcrumb](./concepts.md#breadcrumb).
40
- - **`setTag(key, value)` / `setTags(tags)`** — grava [tags](./concepts.md#tag) no scope global; valem para todos os eventos seguintes. Só `string` → `string`. Precedência: `init.tags` → scope → evento → `hint`.
41
- - **`setUser(user | null)`** — define quem está operando (`id`, `username`, `email`). `null` remove.
42
- - **`setContext(name, ctx | null)`** — define um [context](./concepts.md#context) nomeado. Passar `null` **apaga o grupo inteiro**. Os grupos `runtime` e `os` são injetados pelo SDK em todo evento.
43
- - **`setExtra(key, value)`** — anexa um dado livre, sem estrutura e sem índice. Conceito e critério de escolha em [Extra](./concepts.md#extra).
44
- - **`withScope(cb)`** — roda o callback com um **clone** do scope e restaura o original no fim; devolve o que o callback devolver. É **síncrono** — um `await` dentro do callback não isola de código concorrente. Conceito: [withScope](./concepts.md#withscope).
45
- - **`startTransaction(name, op?)`** — abre uma [transaction](./concepts.md#transaction) de performance. A decisão de [amostragem](./concepts.md#amostragem-samplerate-e-tracessamplerate) acontece **aqui**, não no `finish()`. Nada é enviado até o `finish()`. **Dentro de um handler de task**, devolve a [transaction da própria run](./concepts.md#transaction-de-run-ctxtx) (`ctx.tx`) em vez de criar uma segunda — cada execução tem exatamente uma transaction; fora de um handler, cria uma transaction nova, sem `run_id`.
46
- - **`flush(timeoutMs = 5_000)`** → `Promise<boolean>` — empurra a fila do transport e aguarda os envios em andamento. `true` = tudo saiu no prazo; `false` = o SDK parou de esperar (os envios em voo seguem tentando). O SDK **continua utilizável** depois. Conceito: [Fila, flush e close](./concepts.md#fila-flush-e-close).
47
- - **`close(timeoutMs = 5_000)`** → `Promise<boolean>` — encerra tudo na ordem agente → heartbeat → flush final. O `timeoutMs` é gasto **duas vezes** (espera das runs, depois flush). Depois do `close()` o transport está fechado e todo item novo é descartado com aviso no console; não há `reopen()`. Conceito: [Fila, flush e close](./concepts.md#fila-flush-e-close).
48
-
49
- Classes exportadas: `Client`, `Scope`, `Transaction`, `Step`. A resolução da credencial nos endpoints é interna ao `init()` (constante `DEFAULT_BASE_URL = 'https://api2.hobots.app'`, exportada; em desenvolvimento a env `HOBOTS_API_URL` sobrescreve a URL base). A config resolvida fica acessível em `getClient()?.config`:
16
+ | `hobots.register` | `<P>(task: string, handler: TaskHandler<P>, options?: RegisterOptions) => void` | Task sob demanda. Síncrona: valida slug/handler/secret e lança na hora. O boot roda num microtask — chamadas seguidas entram no primeiro poll. Slug repetido lança. |
17
+ | `hobots.start` | `(task: string, handler: TaskHandler, options?: StartOptions) => void` | Task sempre ligada: abre a própria execução e roda o handler uma vez. |
18
+ | `hobots.close` | `(timeoutMs = 30_000) => Promise<boolean>` | Para o polling, espera as execuções em voo (até `timeoutMs`; as restantes viram `AgentTerminated`), para o heartbeat e faz o flush final. `false` se estourou. Um `register`/`start` depois inicia um runtime novo. |
19
+ | `hobots.captureException` | `(error: unknown, hint?: CaptureHint) => string \| undefined` | Issue no módulo Issues; devolve o `event_id`. Antes de qualquer task: descarta com aviso. |
20
+ | `hobots.retry` | `<T>(fn: (attempt: number) => T \| Promise<T>, options?: RetryOptions) => Promise<T>` | async-retry; dentro de uma execução usa o signal dela. |
50
21
 
51
- ```ts
52
- getClient()?.config; // {
53
- // key: string; — Client Secret do agente (vai no Authorization: Bearer, nunca na URL)
54
- // baseUrl: string; — DEFAULT_BASE_URL, ou HOBOTS_API_URL em desenvolvimento
55
- // ingestEndpoint: string; — <baseUrl>/v1/ingest
56
- // tasksBaseUrl: string; — <baseUrl>/v1/tasks
57
- // }
58
- ```
59
-
60
- ### Métodos de `Transaction` / `Step` / `Scope`
61
-
62
- Todos encadeáveis, exceto `finish()` e `toJSON()`.
63
-
64
- - **`Transaction`** — `startChild(op?, description?) → Step` abre uma [step](./concepts.md#step) direta; `setTag(k, v)` marca a transaction (tags de scope **não** são herdadas); `setStatus(s)` (default `'ok'`); `setName(n)` renomeia antes do envio; `finish()` fecha as etapas abertas, monta o payload e envia — é **idempotente** e não envia nada se a transaction não foi amostrada.
65
- - **`Step`** — `startChild(op?, description?) → Step` aninha uma filha (profundidade arbitrária); `setStatus(s)` (default `'ok'`); `setData(k, v)` anexa dados só àquela step; `finish()` marca o fim — **opcional**, porque o `finish()` da transaction fecha o que ficou aberto; `toJSON()` serializa no formato do wire.
66
- - **`Scope`** — `setTag`/`setTags`/`setUser`/`setContext`/`setExtra`/`setExtras`/`setType`/`setFingerprint`/`addBreadcrumb`/`clearBreadcrumbs`/`clone`. Alcançável via [`withScope`](./concepts.md#withscope). `setContext(name, null)` apaga o grupo; `setUser(null)` remove o usuário.
67
- > **Atenção:** `setType` **não tem efeito** em `captureException` (que já grava `error`) nem em `captureMessage` (que já grava `warning`) — os dois definem o tipo do evento antes de o scope ser consultado. Use o `hint`.
22
+ ## Contexto da execução (`RunContext` — os membros de `hobots` e o `ctx` do handler)
68
23
 
69
- ---
70
-
71
- ## Tasks — funções
72
-
73
- Habilitadas com `tasks: true` (ou `TasksOptions`) no `init()` único. Todas exportadas do import raiz `hobots`:
74
-
75
- | Função | Assinatura |
76
- | --- | --- |
77
- | `register` | `register<P>(task: string, handler: TaskHandler<P>): void` |
78
- | `start` | `start(): void` — lança se o `init()` não configurou `tasks` ou sem tasks registradas |
79
- | `stop` | `stop(timeoutMs = 30_000): Promise<boolean>` — para só o polling de tasks; a telemetria continua |
80
-
81
- ### O que cada função faz
82
-
83
- - **`register(task, handler)`** — associa um handler ao `task_slug` configurado no formulário do app (**Configurações → Formulários**). Registre **antes** de `start()`; pode ser chamado antes do `init()`. Registrar o mesmo `task` duas vezes **sobrescreve** o handler (com aviso no console). Se o `init()` não habilitou as tasks, o SDK avisa e não faz nada. Tipagem dos params: `register<{ lote: number }>(…)`.
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
- - **`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
-
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
-
89
- ---
24
+ | Membro | Tipo | Notas |
25
+ | --- | --- | --- |
26
+ | `runId`, `task` | `string` | Identidade da execução (`''` fora de uma execução) |
27
+ | `params` | `Record<string, unknown>` | Params da solicitação; `{}` no modo start |
28
+ | `log` | `RunLogger` — `debug/info/warn/error/success(message)` | Nunca lança |
29
+ | `items` | `RunItems` — `add(item)`, `succeeded(payload?, opts?)`, `failed(message, payload?, opts?)`, `occurrence(message, payload?, opts?)`; `opts.id` = identidade de negócio | Ponto de parada cooperativo |
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')`, `setData(k, v)`, `finish()` | Finalizada pelo SDK; `startChild` é ponto de parada |
32
+ | `signal` | `AbortSignal` | Cancelamento (app, timeout, shutdown) |
33
+ | `throwIfCanceled()` | `() => void` | Lança `RunCanceled` se o signal disparou |
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
+ | `retry(fn, options?)` | como `hobots.retry` | |
37
+
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 }`).
90
39
 
91
40
  ## Tabelas de opções
92
41
 
93
- ### InitOptions
42
+ ### `HobotsOptions` (processo — a primeira chamada define; divergências avisam e são ignoradas)
94
43
 
95
44
  | Opção | Tipo | Default | Descrição |
96
45
  | --- | --- | --- | --- |
97
- | `clientSecret` | `string` | — | **obrigatório**; o Client Secret do agente (`hb_<64 hex>`), copiado em **Configurações → Agentes** |
98
- | `environment` | `string` | `'production'` | ambiente lógico |
99
- | `release` | `string` | — | versão do programa, ex. `robo-nfe@1.4.2` |
100
- | `instanceId` | `string` | **obrigatório** | identidade única da instância; heartbeat e poll de tasks convergem nela |
101
- | `tags` | `Record<string,string>` | — | tags aplicadas a todos os eventos |
102
- | `sampleRate` | `number` | `1` | amostragem de erros (0..1) |
103
- | `tracesSampleRate` | `number` | `1` | amostragem de transactions (0..1) |
104
- | `maxBreadcrumbs` | `number` | `100` | tamanho do buffer de breadcrumbs |
105
- | `captureUnhandled` | `boolean` | `true` | handlers globais de erro |
106
- | `machineMetrics` | `boolean` | `true` | coleta CPU/memória/disco/processo: vai no heartbeat e no poll (série da instância) e em `contexts.machine` de cada evento; só em Node |
107
- | `heartbeat` | `boolean \| HeartbeatOptions` | `false` | heartbeat periódico |
108
- | `tasks` | `boolean \| TasksOptions` | `false` | habilita a execução sob demanda (`true` usa defaults) |
109
- | `beforeSend` | `(event) => event \| null` | — | edita/descarta evento (retorne `null` para descartar) |
110
- | `debug` | `boolean` | `false` | loga atividade do SDK |
111
- | `flushIntervalMs` | `number` | `2000` | intervalo do flush automático do transport |
112
- | `maxBatchSize` | `number` | `10` | itens por envelope antes do flush imediato |
113
-
114
- O teto da fila do transport (200 itens; ao encher, o **mais antigo** é descartado com aviso) é interno — **não** é opção do `init()`. Conceitos por trás dessas opções: [amostragem](./concepts.md#amostragem-samplerate-e-tracessamplerate), [breadcrumbs](./concepts.md#breadcrumb), [instanceId](./concepts.md#instanceid), [release e environment](./concepts.md#release-e-environment), [beforeSend](./concepts.md#beforesend).
115
-
116
- ### HeartbeatOptions
46
+ | `clientSecret` | `string` | env `HOBOTS_CLIENT_SECRET` | `hb_<64 hex>`; ausente/inválido lança citando a env (sem ecoar o valor) |
47
+ | `instanceId` | `string` | env `HOBOTS_INSTANCE_ID` → `os.hostname()` | identidade estável da instância |
48
+ | `environment` | `'development' \| 'testing' \| 'staging' \| 'production' \| string` | `'production'` | vai em eventos, transactions e na instância; filtra alertas |
49
+ | `release` | `string` | — | versão do programa |
50
+ | `tags` | `Record<string, string>` | — | tags de todos os eventos e transactions |
51
+ | `debug` | `boolean` | `false` | loga a atividade do SDK |
52
+ | `machineMetrics` | `boolean` | `true` | CPU/memória/disco no heartbeat/poll e em `contexts.machine` |
53
+ | `captureUnhandled` | `boolean` | `true` | handlers globais de erro (issues) |
54
+ | `installExitHandlers` | `boolean` | `true` | avançado — SIGINT/SIGTERM/uncaughtException reportam `AgentTerminated` |
55
+
56
+ ### `TaskOptions extends HobotsOptions` (por task)
117
57
 
118
58
  | Opção | Tipo | Default | Descrição |
119
59
  | --- | --- | --- | --- |
120
- | `intervalMs` | `number` | `30000` | intervalo entre batimentos |
121
- | `name` | `string` | o `instanceId` | nome amigável no dashboard |
122
- | `metadata` | `Record<string,unknown>` | — | metadados anexados ao batimento |
123
-
124
- Com `machineMetrics` (default) cada batimento também leva `machine` — um [MachineSnapshot](./protocol.md#machinesnapshot) da máquina.
125
-
126
- ### CaptureHint
127
-
128
- | Campo | Tipo | Descrição |
129
- | --- | --- | --- |
130
- | `type` | `EventType` | tipo do evento |
131
- | `tags` | `Record<string,string>` | tags só deste evento |
132
- | `extra` | `Record<string,unknown>` | dados extras |
133
- | `fingerprint` | `string[]` | agrupamento manual |
134
- | `attachment` | `AttachInput \| CaptureAttachment` | print enviado junto da captura — dentro de uma execução de task, vira anexo da linha de log da run que espelha o evento; fora de uma execução é descartado com aviso. Guia: [Anexos](./tasks.md#anexos-screenshots) |
135
-
136
- ### CaptureAttachment
60
+ | `retries` | `number` | `1` | tentativas extras do handler; `0` desliga |
61
+ | `retryDelayMs` | `number \| (attempt) => number` | `5_000` | `attempt` 1-based (a que falhou) |
62
+ | `timeoutMs` | `number` | — | por tentativa; terminal (`TaskTimeout`) |
63
+ | `logFlushIntervalMs` | `number` | `2_000` | avançado |
64
+ | `cancelCheckIntervalMs` | `number` | `5_000` (1_000..15_000) | avançado — PATCH vazio que detecta cancelamento e é o sinal de vida da execução |
65
+ | `cancelGraceMs` | `number` | `10_000` | avançado — graça após o signal antes de liberar o slot |
137
66
 
138
- `AttachInput = Uint8Array | ArrayBuffer | string` (bytes ou um caminho de arquivo, só em Node). Para legenda ou content-type, passe o objeto:
67
+ ### `RegisterOptions extends TaskOptions`
139
68
 
140
- | Campo | Tipo | Default | O que faz |
69
+ | Opção | Tipo | Default | Descrição |
141
70
  | --- | --- | --- | --- |
142
- | `image` | `AttachInput` | — | a imagem em si |
143
- | `caption` | `string` | — | legenda exibida sob a imagem ampliada; truncada em 200 caracteres e passada pelo scrub de segredos |
144
- | `contentType` | `'image/png' \| 'image/jpeg' \| 'image/webp'` | detectado pelos *magic bytes* | só usado quando a detecção falha; a detecção sempre tem precedência |
71
+ | `concurrency` | `number` | `1` | execuções simultâneas desta task (1..5) |
72
+ | `pollIntervalMs` | `number` | `2_000` (mín. 1_000) | intervalo entre polls sem trabalho; vale o do primeiro `register()` |
145
73
 
146
- O texto da linha de log é a mensagem capturada; o nível vem do `level` da captura (`'fatal'` vira `'error'`). O upload roda em segundo plano e **nunca bloqueia nem faz a captura lançar**. Limites (todos aplicados também no servidor): PNG/JPEG/WEBP, **5 MB** por imagem, **20** anexos por execução, **4** por linha de log.
74
+ `StartOptions = TaskOptions`.
147
75
 
148
- `EventType = 'fatal' | 'error' | 'warning' | 'success'`. O que cada tipo significa (e por que `'success'` existe) em [Type](./concepts.md#type-tipo-do-evento).
76
+ ### `RetryOptions`
149
77
 
150
- ### TasksOptions
78
+ | Opção | Tipo | Default |
79
+ | --- | --- | --- |
80
+ | `retries` | `number` | `3` |
81
+ | `delayMs` | `number \| (attempt) => number` | `1_000` |
82
+ | `shouldRetry` | `(error) => boolean` | tudo, exceto `RunCanceled`/`TaskTimeout`/`AbortError` (nunca re-tentados) |
151
83
 
152
- Valor do campo `tasks` do `InitOptions`. O Client Secret, o `debug` e a identidade (`instanceId`) vêm do `init()`.
84
+ ### `CaptureHint`
153
85
 
154
- | Opção | Tipo | Default | Descrição |
155
- | --- | --- | --- | --- |
156
- | `pollIntervalMs` | `number` | `5000` | intervalo entre polls sem trabalho (mínimo `1000`) |
157
- | `concurrency` | `number` | `1` | runs simultâneas (clamp 1..5) |
158
- | `taskTimeoutMs` | `number` | — | timeout client-side por run (não mata o handler; servidor é o backstop) |
159
- | `logFlushIntervalMs` | `number` | `2000` | intervalo do envio incremental de logs |
160
- | `cancelCheckIntervalMs` | `number` | `5000` | heartbeat de cancelamento por run (clamp 1000..15000) — também é o sinal de vida da run (servidor marca `AgentTerminated` após ~60s sem reporte) |
161
- | `installExitHandlers` | `boolean` | `true` | handlers de SIGINT/SIGTERM/uncaughtException que reportam as runs ativas como `AgentTerminated` antes do processo morrer; nunca interferem quando o programa tem handler próprio |
86
+ | Campo | Tipo | Descrição |
87
+ | --- | --- | --- |
88
+ | `type` | `'fatal' \| 'error' \| 'warning' \| 'success'` | default `'error'` |
89
+ | `tags`, `extra`, `fingerprint` | | metadados do evento (`fingerprint` sobrescreve o agrupamento) |
90
+ | `attachment` | `AttachInput \| { image, caption?, contentType? }` | print anexado à linha de log da execução atual |
162
91
 
163
- ### TaskHandler / RunContext
92
+ ### `TaskHandler`
164
93
 
165
94
  ```ts
166
- // o retorno do handler é ignorado — nada além de status/logs/erro é enviado ao servidor
167
- type TaskHandler<P = Record<string, unknown>> =
168
- (params: P, ctx: RunContext) => void | Promise<void>;
169
-
170
- interface RunContext {
171
- runId: string;
172
- task: string;
173
- log: RunLogger; // ctx.log.debug/info/warn/error/success(message)
174
- items: RunItems; // ctx.items.add/succeeded/failed/occurrence — a entrega do bot
175
- progress: RunProgressReporter; // ctx.progress.total/advance/set — barra + tempo restante (opcional)
176
- tx: Transaction; // transaction desta run — correlacionada e finalizada pelo SDK
177
- signal: AbortSignal;
178
- throwIfCanceled(): void; // lança RunCanceled se o signal já disparou
179
- sleep(ms: number): Promise<void>; // espera cancelável — rejeita com RunCanceled no abort
180
- }
181
-
182
- interface RunItems {
183
- add(item: { status: RunItemStatus; id?: string; message?: string; payload?: Record<string, unknown> }): void;
184
- succeeded(payload?: Record<string, unknown>, options?: { id?: string }): void;
185
- failed(message: string, payload?: Record<string, unknown>, options?: { id?: string }): void;
186
- occurrence(message: string, payload?: Record<string, unknown>, options?: { id?: string }): void;
187
- }
188
-
189
- interface RunProgressReporter {
190
- total(total: number): void; // declara/ajusta o total de itens (inteiro >= 1; idempotente)
191
- advance(count?: number): void; // incrementa os processados (padrão 1; clampa em total)
192
- set(current: number, total?: number): void; // valor absoluto
193
- readonly current: number;
194
- readonly snapshot: RunProgress | null; // { current, total } ou null sem total declarado
195
- }
95
+ type TaskHandler<P = Record<string, unknown>> = (params: P, ctx: RunContext) => void | Promise<void>;
196
96
  ```
197
97
 
198
- - **`runId` / `task`** — identificam a execução; úteis para correlacionar com os seus próprios logs.
199
- - **`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).
200
- - **`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).
201
- - **`progress.total(n)` / `progress.advance()` / `progress.set(c, t?)`** — progresso declarado da run (itens processados / total), opcional e independente de `items`; o app mostra barra, percentual e tempo restante. Síncrono; o snapshot pega carona nos PATCHes de log e sai no máximo 1×/s por conta própria. Guia: [Progresso da execução](./tasks.md#progresso-da-execução-ctxprogress).
202
- - **`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.
203
- - **`signal`** — `AbortSignal` que dispara em cancelamento no app, timeout ou shutdown.
204
- - **`throwIfCanceled()`** — o ponto de parada de uma linha: lança `RunCanceled` se o `signal` já disparou.
205
- - **`sleep(ms)`** — espera que respeita o `signal` (rejeita com `RunCanceled` em vez de dormir até o fim). Use no lugar do seu `sleep` em esperas longas.
206
-
207
- Para anexar um print a uma linha de log da run, use o campo `attachment` do [`CaptureHint`](#capturehint) em `captureException`/`captureMessage` dentro do handler. Guia: [Anexos](./tasks.md#anexos-screenshots).
208
-
209
- ---
210
-
211
98
  ## Tempos e intervalos
212
99
 
213
- Todos os prazos do SDK num lugar só. A coluna **Onde configura** diz se o valor é seu ou fixo — o que está com `—` é comportamento do SDK ou do servidor e não se muda pelo `init()`.
214
-
215
- ### Telemetria — envio
216
-
217
- | Tempo | Default | Onde configura | O que acontece |
218
- | --- | --- | --- | --- |
219
- | flush automático | `2000` ms | `init({ flushIntervalMs })` | de quanto em quanto o transport esvazia a fila; menor = mais requests, telemetria mais fresca |
220
- | gatilho de flush por lote | `10` itens | `init({ maxBatchSize })` | ao atingir, envia **na hora** sem esperar o intervalo; é só o gatilho, não o tamanho do envelope |
221
- | teto de itens por envelope | `50` itens | — (servidor) | limite aceito pelo `/v1/ingest`; a fila é fatiada em quantos envelopes forem necessários, um POST por lote |
222
- | teto da fila | `200` itens | — (fixo) | fila cheia descarta o item **mais antigo**, com aviso no console |
223
- | `flush(timeoutMs)` | `5000` ms | argumento | quanto espera os envios em voo antes de devolver `false` |
224
- | `close(timeoutMs)` | `5000` ms | argumento | idem, no flush final (veja a nota 1 abaixo) |
225
- | ladder de retry | `1s, 4s, 16s` | — (fixo) | 3 tentativas por envelope; depois ele é descartado. 4xx (exceto 429) não tem retry |
226
- | heartbeat | `30000` ms | `init({ heartbeat: { intervalMs } })` | bate **imediatamente** no `init()` e depois a cada intervalo; o valor vai no payload e define quanto silêncio o servidor tolera antes de marcar offline |
227
-
228
- ### Tasks — polling e runs
229
-
230
- | Tempo | Default | Onde configura | O que acontece |
231
- | --- | --- | --- | --- |
232
- | 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)) |
233
- | flush de logs da run | `2000` ms | `tasks: { logFlushIntervalMs }` | ou na hora, ao acumular 20 linhas |
234
- | flush de itens da run | `2000` ms | — (acompanha `logFlushIntervalMs`) | ou na hora, ao acumular 100 itens; POSTs de até 500 |
235
- | 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 |
236
- | validade da URL de upload de anexo | `900` s | — (servidor) | tempo para o PUT no S3 depois que a API presigna |
237
- | 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" |
238
- | uploads de anexo simultâneos | `3` | — (fixo) | por execução; os demais ficam na fila |
239
- | retry do upload de anexo | `1s, 4s` | — (fixo) | ladder curta: o binário é grande e a URL presignada tem prazo |
240
- | heartbeat de cancelamento | `5000` ms (clamp `1000`..`15000`) | `tasks: { cancelCheckIntervalMs }` | detecta cancelamento feito no app **e** é o sinal de vida da run (veja a nota 2) |
241
- | timeout por run | sem timeout | `tasks: { taskTimeoutMs }` | no estouro dispara `ctx.signal`, libera o slot de concorrência e reporta `TaskTimeout` — **não mata** o handler |
242
- | `stop(timeoutMs)` | `30000` ms | argumento | espera as runs ativas; no estouro elas são reportadas como `AgentTerminated` |
243
- | retry do poll | `1s, 4s, 16s` | — (fixo) | erros de rede/5xx |
244
- | retry com chave rejeitada (401) | `30000` ms | — (fixo) | segue tentando devagar, com log throttled |
245
- | PATCH do caminho de saída | `~2500` ms | — (fixo) | melhor esforço, sem retry, ao morrer com run ativa |
246
- | sweep do servidor | `~60000` ms | — (servidor) | run `running` sem reporte por esse tempo vira `failed`/`AgentTerminated`, sem re-entrega |
247
-
248
- **Nota 1 — `close(timeoutMs)` gasta o tempo duas vezes.** Ele espera as runs de tasks (`timeoutMs`) e **depois** faz o flush final (`timeoutMs`), em sequência. Pior caso ≈ `2 × timeoutMs`; `close()` sem argumento pode levar até ~35s (30s aguardando runs + 5s de flush). Se o seu SIGTERM tem prazo curto (Kubernetes, systemd), dimensione por aí.
249
-
250
- **Nota 2 — o teto de 15s do `cancelCheckIntervalMs` existe por causa do sweep de 60s.** Esse heartbeat é o que mantém o `last_report_at` da run atualizado; o clamp garante que o intervalo nunca chegue perto do prazo em que o servidor declara a run morta.
100
+ | O quê | Valor |
101
+ | --- | --- |
102
+ | Heartbeat | 30 s (±10 % de jitter), sempre ligado, timer ref'd (segura o processo) |
103
+ | Flush da telemetria | 2 s ou 10 itens; envelopes de até 50 itens; fila de 200 |
104
+ | Retry HTTP (ingest, PATCH, itens, anexos) | 1 s / 4 s / 16 s com jitter; 4xx ≠ 429 é definitivo |
105
+ | Poll sem trabalho | `pollIntervalMs` (2 s); com trabalho, imediato (drain) |
106
+ | Poll/criação de run com erro | 1 s / 4 s / 16 s; 401 → 30 s com erro logado na 1ª e a cada 10 falhas |
107
+ | Logs da execução | flush a cada `logFlushIntervalMs` (2 s) ou 20 linhas; chunks de 100; 1000 por execução no servidor |
108
+ | Cancel-watch / sinal de vida | `cancelCheckIntervalMs` (5 s); o servidor marca `AgentTerminated` após ~120 s sem PATCH |
109
+ | Retry do handler | `retries` 1, `retryDelayMs` 5 s |
110
+ | Drenos antes do terminal | anexos 15 s, itens 15 s |
111
+ | `close()` | 30 s para as execuções em voo; exit path reporta em ≤ 2,5 s |