hobots 2.4.0 → 3.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.
@@ -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\nimport type { EnvironmentInput } from './environment-protocol';\nimport type { MachineSnapshot } from './machine-protocol';\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 RunProgress,\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\n// Snapshot de recursos da máquina — usado no heartbeat, no poll e em contexts.machine.\nexport type { MachineProcess, MachineSnapshot } from './machine-protocol';\nexport type { Environment, EnvironmentInput } from './environment-protocol';\n\n/**\n * Tipo de um evento — o que ele é, não o quanto ele fala. Só 'fatal', 'error' e\n * 'warning' viram issue; 'success' é ingerido e nunca abre issue.\n * Ruído de log não é evento: para isso existem os logs da execução (RunLogLevel)\n * e as breadcrumbs, que têm níveis próprios com 'debug'/'info'.\n */\nexport type EventType = 'fatal' | 'error' | 'warning' | 'success';\n\n/** Nível de uma breadcrumb — trilha do que aconteceu antes, aqui log é bem-vindo. */\nexport type BreadcrumbLevel = 'debug' | 'info' | 'warning' | 'error' | 'fatal';\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?: BreadcrumbLevel;\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 type: EventType;\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?: EnvironmentInput;\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?: EnvironmentInput;\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 /** Ambiente da instância — é ele que o app exibe e que as regras de alerta filtram */\n environment?: EnvironmentInput;\n /** Recursos da máquina no momento do beat (CPU, memória, disco, processo) */\n machine?: MachineSnapshot;\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\nimport type { EnvironmentInput } from './environment-protocol';\nimport type { MachineSnapshot } from './machine-protocol';\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 RunCreateRequest,\n RunCreateResponse,\n RunError,\n RunProgress,\n RunUpdateRequest,\n RunUpdateResponse,\n RunItemStatus,\n RunItemInput,\n RunItemsCreateRequest,\n RunItemsCreateResponse,\n ItemColumn,\n TaskItemsDeclaration,\n RunAttachmentContentType,\n RunAttachmentCreateRequest,\n RunAttachmentCreateResponse,\n RunAttachmentConfirmRequest,\n RunAttachmentConfirmResponse,\n} from './tasks/protocol';\n\n// Snapshot de recursos da máquina — usado no heartbeat, no poll e em contexts.machine.\nexport type { MachineProcess, MachineSnapshot } from './machine-protocol';\nexport type { Environment, EnvironmentInput } from './environment-protocol';\n\n/**\n * Tipo de um evento — o que ele é, não o quanto ele fala. Só 'fatal', 'error' e\n * 'warning' viram issue; 'success' é ingerido e nunca abre issue.\n * Ruído de log não é evento: para isso existem os logs da execução (RunLogLevel)\n * e as breadcrumbs, que têm níveis próprios com 'debug'/'info'.\n */\nexport type EventType = 'fatal' | 'error' | 'warning' | 'success';\n\n/** Nível de uma breadcrumb — trilha do que aconteceu antes, aqui log é bem-vindo. */\nexport type BreadcrumbLevel = 'debug' | 'info' | 'warning' | 'error' | 'fatal';\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?: BreadcrumbLevel;\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 type: EventType;\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?: EnvironmentInput;\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?: EnvironmentInput;\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 /** Ambiente da instância — é ele que o app exibe e que as regras de alerta filtram */\n environment?: EnvironmentInput;\n /** Recursos da máquina no momento do beat (CPU, memória, disco, processo) */\n machine?: MachineSnapshot;\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":[]}
@@ -96,6 +96,16 @@ interface RunLogEntry {
96
96
  */
97
97
  attachments?: string[];
98
98
  }
99
+ /** Uma coluna da aba "itens" da execução: chave do payload dos itens + rótulo de exibição. */
100
+ interface ItemColumn {
101
+ key: string;
102
+ /** ausente = a plataforma exibe a própria key */
103
+ label?: string;
104
+ }
105
+ /** Colunas de itens declaradas no código para uma task (`items.columns` do register/start). */
106
+ interface TaskItemsDeclaration {
107
+ attributes: ItemColumn[];
108
+ }
99
109
  interface InstanceInfo {
100
110
  /** Identidade canônica do agente — correlaciona com o heartbeat de telemetria */
101
111
  instance_id: string;
@@ -108,6 +118,8 @@ interface InstanceInfo {
108
118
  environment?: EnvironmentInput;
109
119
  /** Tasks registradas no agente */
110
120
  tasks?: string[];
121
+ /** Colunas de itens declaradas no código, por task — só as tasks que declararam `items` */
122
+ task_items?: Record<string, TaskItemsDeclaration>;
111
123
  /**
112
124
  * Intervalo de poll em ms — alimenta a tolerância de offline do servidor (3×) em
113
125
  * agentes poll-only, que sem isso herdariam a tolerância do heartbeat (30s × 3).
@@ -132,7 +144,7 @@ interface RunDescriptor {
132
144
  id: string;
133
145
  name: string;
134
146
  } | null;
135
- /** Origem da run: 'hobots' | 'link' | 'schedule' (informativo) */
147
+ /** Origem da run: 'hobots' | 'link' | 'schedule' | 'agent' (informativo; 'agent' = aberta pelo próprio SDK, modo start) */
136
148
  source: string;
137
149
  /** ISO 8601 (relógio do servidor) */
138
150
  created_at: string;
@@ -140,6 +152,19 @@ interface RunDescriptor {
140
152
  interface PollResponse {
141
153
  runs: RunDescriptor[];
142
154
  }
155
+ /**
156
+ * POST /v1/tasks/runs — modo start do SDK: a própria instância abre a run, que já
157
+ * nasce 'running' (started_at = now), sem formulário nem solicitante. Dali em diante
158
+ * ela é reportada pelas mesmas rotas de uma run reivindicada no poll.
159
+ */
160
+ interface RunCreateRequest {
161
+ /** slug da task (1..255) — vira task_runs.task_slug */
162
+ task: string;
163
+ /** presença da instância — obrigatória: a run nasce vinculada à instância */
164
+ instance: InstanceInfo;
165
+ }
166
+ /** Resposta (201): o mesmo descritor do poll, com params {}, form null e source 'agent'. */
167
+ type RunCreateResponse = RunDescriptor;
143
168
  interface RunError {
144
169
  /** ex.: nome da classe do erro */
145
170
  type?: string;
@@ -363,4 +388,4 @@ interface Envelope {
363
388
  items: EnvelopeItem[];
364
389
  }
365
390
 
366
- export type { Breadcrumb, BreadcrumbLevel, Envelope, EnvelopeItem, Environment, EnvironmentInput, EventPayload, EventType, EventUser, ExceptionData, HeartbeatPayload, InstanceInfo, MachineProcess, MachineSnapshot, PollRequest, PollResponse, RunAttachmentConfirmRequest, RunAttachmentConfirmResponse, RunAttachmentContentType, RunAttachmentCreateRequest, RunAttachmentCreateResponse, RunDescriptor, RunError, RunItemInput, RunItemStatus, RunItemsCreateRequest, RunItemsCreateResponse, RunLogEntry, RunLogLevel, RunProgress, RunStatus, RunUpdateRequest, RunUpdateResponse, RunUpdateStatus, StackFrame, StepData, StepStatus, TransactionPayload };
391
+ export type { Breadcrumb, BreadcrumbLevel, Envelope, EnvelopeItem, Environment, EnvironmentInput, EventPayload, EventType, EventUser, ExceptionData, HeartbeatPayload, InstanceInfo, ItemColumn, MachineProcess, MachineSnapshot, PollRequest, PollResponse, RunAttachmentConfirmRequest, RunAttachmentConfirmResponse, RunAttachmentContentType, RunAttachmentCreateRequest, RunAttachmentCreateResponse, RunCreateRequest, RunCreateResponse, RunDescriptor, RunError, RunItemInput, RunItemStatus, RunItemsCreateRequest, RunItemsCreateResponse, RunLogEntry, RunLogLevel, RunProgress, RunStatus, RunUpdateRequest, RunUpdateResponse, RunUpdateStatus, StackFrame, StepData, StepStatus, TaskItemsDeclaration, TransactionPayload };
@@ -96,6 +96,16 @@ interface RunLogEntry {
96
96
  */
97
97
  attachments?: string[];
98
98
  }
99
+ /** Uma coluna da aba "itens" da execução: chave do payload dos itens + rótulo de exibição. */
100
+ interface ItemColumn {
101
+ key: string;
102
+ /** ausente = a plataforma exibe a própria key */
103
+ label?: string;
104
+ }
105
+ /** Colunas de itens declaradas no código para uma task (`items.columns` do register/start). */
106
+ interface TaskItemsDeclaration {
107
+ attributes: ItemColumn[];
108
+ }
99
109
  interface InstanceInfo {
100
110
  /** Identidade canônica do agente — correlaciona com o heartbeat de telemetria */
101
111
  instance_id: string;
@@ -108,6 +118,8 @@ interface InstanceInfo {
108
118
  environment?: EnvironmentInput;
109
119
  /** Tasks registradas no agente */
110
120
  tasks?: string[];
121
+ /** Colunas de itens declaradas no código, por task — só as tasks que declararam `items` */
122
+ task_items?: Record<string, TaskItemsDeclaration>;
111
123
  /**
112
124
  * Intervalo de poll em ms — alimenta a tolerância de offline do servidor (3×) em
113
125
  * agentes poll-only, que sem isso herdariam a tolerância do heartbeat (30s × 3).
@@ -132,7 +144,7 @@ interface RunDescriptor {
132
144
  id: string;
133
145
  name: string;
134
146
  } | null;
135
- /** Origem da run: 'hobots' | 'link' | 'schedule' (informativo) */
147
+ /** Origem da run: 'hobots' | 'link' | 'schedule' | 'agent' (informativo; 'agent' = aberta pelo próprio SDK, modo start) */
136
148
  source: string;
137
149
  /** ISO 8601 (relógio do servidor) */
138
150
  created_at: string;
@@ -140,6 +152,19 @@ interface RunDescriptor {
140
152
  interface PollResponse {
141
153
  runs: RunDescriptor[];
142
154
  }
155
+ /**
156
+ * POST /v1/tasks/runs — modo start do SDK: a própria instância abre a run, que já
157
+ * nasce 'running' (started_at = now), sem formulário nem solicitante. Dali em diante
158
+ * ela é reportada pelas mesmas rotas de uma run reivindicada no poll.
159
+ */
160
+ interface RunCreateRequest {
161
+ /** slug da task (1..255) — vira task_runs.task_slug */
162
+ task: string;
163
+ /** presença da instância — obrigatória: a run nasce vinculada à instância */
164
+ instance: InstanceInfo;
165
+ }
166
+ /** Resposta (201): o mesmo descritor do poll, com params {}, form null e source 'agent'. */
167
+ type RunCreateResponse = RunDescriptor;
143
168
  interface RunError {
144
169
  /** ex.: nome da classe do erro */
145
170
  type?: string;
@@ -363,4 +388,4 @@ interface Envelope {
363
388
  items: EnvelopeItem[];
364
389
  }
365
390
 
366
- export type { Breadcrumb, BreadcrumbLevel, Envelope, EnvelopeItem, Environment, EnvironmentInput, EventPayload, EventType, EventUser, ExceptionData, HeartbeatPayload, InstanceInfo, MachineProcess, MachineSnapshot, PollRequest, PollResponse, RunAttachmentConfirmRequest, RunAttachmentConfirmResponse, RunAttachmentContentType, RunAttachmentCreateRequest, RunAttachmentCreateResponse, RunDescriptor, RunError, RunItemInput, RunItemStatus, RunItemsCreateRequest, RunItemsCreateResponse, RunLogEntry, RunLogLevel, RunProgress, RunStatus, RunUpdateRequest, RunUpdateResponse, RunUpdateStatus, StackFrame, StepData, StepStatus, TransactionPayload };
391
+ export type { Breadcrumb, BreadcrumbLevel, Envelope, EnvelopeItem, Environment, EnvironmentInput, EventPayload, EventType, EventUser, ExceptionData, HeartbeatPayload, InstanceInfo, ItemColumn, MachineProcess, MachineSnapshot, PollRequest, PollResponse, RunAttachmentConfirmRequest, RunAttachmentConfirmResponse, RunAttachmentContentType, RunAttachmentCreateRequest, RunAttachmentCreateResponse, RunCreateRequest, RunCreateResponse, RunDescriptor, RunError, RunItemInput, RunItemStatus, RunItemsCreateRequest, RunItemsCreateResponse, RunLogEntry, RunLogLevel, RunProgress, RunStatus, RunUpdateRequest, RunUpdateResponse, RunUpdateStatus, StackFrame, StepData, StepStatus, TaskItemsDeclaration, TransactionPayload };
package/docs/README.md CHANGED
@@ -1,49 +1,32 @@
1
1
  # Documentação — hobots
2
2
 
3
- SDK JavaScript da **Hobots** para instrumentar agentes (RPAs) e integrações. Cliente HTTP puro, sem dependências de runtime, para Node 18+ (a telemetria também roda no browser para captura de erros).
3
+ SDK JavaScript da **Hobots** para robôs (RPAs) e integrações. Cliente HTTP puro, sem dependências de runtime, Node 18+.
4
4
 
5
- O SDK expõe **duas capabilities** autenticadas pelo **mesmo Client Secret do agente**, configuradas em **um único `init()`** com **um único import** (`import * as Hobots from 'hobots'`):
5
+ **Tudo é uma task.** Não existe `init()`: a primeira chamada a `register()`/`start()` configura o processo (credenciais do ambiente, heartbeat sempre ligado, captura de erros não tratados). O objeto global `hobots` expõe a execução atual — `hobots.log`, `hobots.items`, `hobots.progress`, `hobots.transaction`… — de qualquer lugar do programa.
6
6
 
7
- | Capability | Como habilita | Para quê |
7
+ | Modo | Chamada | Para quê |
8
8
  | --- | --- | --- |
9
- | **Telemetria** | sempre ativa no `init()` | O que o programa **emite** sozinho: erros com stack trace (módulo **Issues**), performance (transactions/etapas) e heartbeats de uptime (módulo **Agentes**). Instala handlers globais de erro; os timers são `unref`'d, então nunca seguram o processo vivo. |
10
- | **Tasks** | `tasks: true` (ou opções) no `init()` | Execução **sob demanda**: o agente faz polling no app, executa handlers registrados e reporta status/logs — o módulo **Solicitações**. `start()` **mantém o processo vivo** até `stop()`/`close()`. |
9
+ | **Sob demanda** | `hobots.register(task, handler, options?)` | O robô faz polling e executa uma solicitação do app (formulário/agendamento) por vez — módulo **Solicitações**. |
10
+ | **Sempre ligado** | `hobots.start(task, handler, options?)` | O robô abre a própria execução e roda o handler (o seu loop) dentro dela; a plataforma recebe logs, itens e progresso. |
11
11
 
12
12
  ## Páginas
13
13
 
14
- Se termos como transaction, step, breadcrumb, tag, context, extra, flush e close ainda não são familiares, comece por **Conceitos** — as outras páginas assumem esse vocabulário.
15
-
16
- - [Conceitos](./concepts.md) — o vocabulário: o que cada coisa é, por que existe e quando escolher uma em vez da outra.
17
- - [Getting started](./getting-started.md) — instalação, Client Secret, o `init` único, telemetria e tasks.
18
- - [Telemetria](./telemetry.md) — erros, breadcrumbs, escopo, transactions/etapas e heartbeat.
19
- - [Tasks](./tasks.md) — registro de handlers, `RunContext`, cancelamento, concorrência, idempotência.
20
- - [Referência de API](./api-reference.md) — todas as funções e tabelas de opções com defaults.
21
- - [Protocolo & transporte](./protocol.md) — o Client Secret, endpoints, envelope, retries e onde vive o claim atômico.
14
+ - [Getting started](./getting-started.md) — instalação, envs, primeira task nos dois modos, encerramento.
15
+ - [Tasks](./tasks.md) — handler, o objeto `hobots`, itens, progresso, anexos, cancelamento automático, retry, concorrência, idempotência.
16
+ - [Conceitos](./concepts.md) — o vocabulário: task, execução, evento, issue, transaction, step, heartbeat, instância, claim.
17
+ - [Referência de API](./api-reference.md) — assinaturas, tabelas de opções e tempos.
18
+ - [Protocolo & transporte](./protocol.md) — Client Secret, endpoints (`/v1/ingest`, `/v1/tasks/*`), envelope, retries, claim atômico.
22
19
 
23
20
  ## Exemplo mínimo
24
21
 
25
22
  ```js
26
- import * as Hobots from 'hobots';
27
-
28
- // O Client Secret do agente — copie em Configurações → Agentes no app
29
- const clientSecret = 'hb_<64 hex>';
30
-
31
- // Um init só: telemetria sempre ativa; tasks habilita a execução sob demanda.
32
- // O instanceId é a identidade única — o robô aparece como um agente só no painel.
33
- Hobots.init({
34
- clientSecret,
35
- instanceId: 'vm-cliente-01',
36
- heartbeat: true,
37
- tasks: true,
38
- });
23
+ import hobots from 'hobots'; // HOBOTS_CLIENT_SECRET e HOBOTS_INSTANCE_ID no ambiente
39
24
 
40
- Hobots.register('processar-nfe', async (params, ctx) => {
41
- ctx.log.info(`processando lote ${params.lote}…`);
42
- ctx.log.info('12 notas processadas'); // o retorno do handler é ignorado — reporte por log
25
+ hobots.register('processar-nfe', async params => {
26
+ hobots.log.info(`processando lote ${params.lote}…`);
27
+ hobots.items.succeeded({ numero_nota: '123' }, { id: '123' });
28
+ hobots.log.success('concluído'); // o retorno do handler é ignorado — reporte por log/itens
43
29
  });
44
- Hobots.start(); // mantém o processo vivo aguardando execuções
45
30
  ```
46
31
 
47
- > Exemplos runnable de toda a API pública (incluindo as duas capabilities juntas em
48
- > [`full.ts`](../../hobots-sdk-demos/src/full.ts)) estão em
49
- > [`packages/hobots-sdk-demos`](../../hobots-sdk-demos).
32
+ > Exemplos runnable em [`packages/hobots-sdk-demos`](../../hobots-sdk-demos).
@@ -4,247 +4,109 @@
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, ...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 | 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` | `'development' \| 'testing' \| 'staging' \| 'production'` | `'production'` | ambiente lógico — vai nos eventos **e** na identidade da instância; é por ele que as regras de alerta decidem se notificam |
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 |
66
+ | `items` | `{ columns: Array<string \| { key, label? }> }` | — | colunas da aba de itens no app (chaves do `payload` dos itens); até 12, chave `[a-zA-Z0-9_.-]`. Independe de formulário; personalizar no app sobrepõe |
137
67
 
138
- `AttachInput = Uint8Array | ArrayBuffer | string` (bytes ou um caminho de arquivo, só em Node). Para legenda ou content-type, passe o objeto:
68
+ ### `RegisterOptions extends TaskOptions`
139
69
 
140
- | Campo | Tipo | Default | O que faz |
70
+ | Opção | Tipo | Default | Descrição |
141
71
  | --- | --- | --- | --- |
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 |
72
+ | `concurrency` | `number` | `1` | execuções simultâneas desta task (1..5) |
73
+ | `pollIntervalMs` | `number` | `2_000` (mín. 1_000) | intervalo entre polls sem trabalho; vale o do primeiro `register()` |
145
74
 
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.
75
+ `StartOptions = TaskOptions`.
147
76
 
148
- `EventType = 'fatal' | 'error' | 'warning' | 'success'`. O que cada tipo significa (e por que `'success'` existe) em [Type](./concepts.md#type-tipo-do-evento).
77
+ ### `RetryOptions`
149
78
 
150
- ### TasksOptions
79
+ | Opção | Tipo | Default |
80
+ | --- | --- | --- |
81
+ | `retries` | `number` | `3` |
82
+ | `delayMs` | `number \| (attempt) => number` | `1_000` |
83
+ | `shouldRetry` | `(error) => boolean` | tudo, exceto `RunCanceled`/`TaskTimeout`/`AbortError` (nunca re-tentados) |
151
84
 
152
- Valor do campo `tasks` do `InitOptions`. O Client Secret, o `debug` e a identidade (`instanceId`) vêm do `init()`.
85
+ ### `CaptureHint`
153
86
 
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 |
87
+ | Campo | Tipo | Descrição |
88
+ | --- | --- | --- |
89
+ | `type` | `'fatal' \| 'error' \| 'warning' \| 'success'` | default `'error'` |
90
+ | `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 |
162
92
 
163
- ### TaskHandler / RunContext
93
+ ### `TaskHandler`
164
94
 
165
95
  ```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
- }
96
+ type TaskHandler<P = Record<string, unknown>> = (params: P, ctx: RunContext) => void | Promise<void>;
196
97
  ```
197
98
 
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
99
  ## Tempos e intervalos
212
100
 
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.
101
+ | O quê | Valor |
102
+ | --- | --- |
103
+ | Heartbeat | 30 s (±10 % de jitter), sempre ligado, timer ref'd (segura o processo) |
104
+ | Flush da telemetria | 2 s ou 10 itens; envelopes de até 50 itens; fila de 200 |
105
+ | Retry HTTP (ingest, PATCH, itens, anexos) | 1 s / 4 s / 16 s com jitter; 4xx ≠ 429 é definitivo |
106
+ | Poll sem trabalho | `pollIntervalMs` (2 s); com trabalho, imediato (drain) |
107
+ | 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 |
108
+ | Logs da execução | flush a cada `logFlushIntervalMs` (2 s) ou 20 linhas; chunks de 100; 1000 por execução no servidor |
109
+ | Cancel-watch / sinal de vida | `cancelCheckIntervalMs` (5 s); o servidor marca `AgentTerminated` após ~120 s sem PATCH |
110
+ | Retry do handler | `retries` 1, `retryDelayMs` 5 s |
111
+ | Drenos antes do terminal | anexos 15 s, itens 15 s |
112
+ | `close()` | 30 s para as execuções em voo; exit path reporta em ≤ 2,5 s |