hobots 4.5.0 → 5.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.
- package/README.md +14 -11
- package/dist/cli.cjs +250 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/index.cjs +544 -222
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +75 -250
- package/dist/index.d.ts +75 -250
- package/dist/index.js +544 -222
- package/dist/index.js.map +1 -1
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.cts +5 -2
- package/dist/protocol.d.ts +5 -2
- package/docs/README.md +2 -2
- package/docs/api-reference.md +21 -7
- package/docs/concepts.md +11 -11
- package/docs/getting-started.md +1 -1
- package/docs/protocol.md +5 -5
- package/docs/runs.md +97 -23
- package/package.json +4 -1
package/dist/protocol.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/protocol.ts","../protocol-limits.json","../src/wire-limits.ts"],"sourcesContent":["/**\n * Tipos do protocolo do Hobots (SDK → servidor).\n *\n * Este arquivo é a fonte de verdade do wire format: reexporta tipos (o app valida\n * os payloads com zod e trava o drift via `satisfies z.ZodType<...>` contra eles)\n * e os LIMITES numéricos do wire (WIRE_LIMITS, de protocol-limits.json) — o único\n * valor de runtime daqui, para a api aplicar exatamente os caps que o SDK aplica.\n */\n\nexport { WIRE_LIMITS, type WireLimits } from './wire-limits';\n\nimport type { EnvironmentInput } from './environment-protocol';\nimport type { MachineSnapshot } from './machine-protocol';\n\n// Wire format de execuções (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 RunStartRequest,\n RunStartResponse,\n RunError,\n RunProgress,\n RunUpdateRequest,\n RunUpdateResponse,\n RunItemStatus,\n RunItemInput,\n RunItemsCreateRequest,\n RunItemsCreateResponse,\n ItemColumn,\n ItemsDeclaration,\n InstanceMode,\n RunAttachmentContentType,\n RunAttachmentCreateRequest,\n RunAttachmentCreateResponse,\n RunAttachmentConfirmRequest,\n RunAttachmentConfirmResponse,\n FileStorage,\n FileRef,\n FileDownloadResponse,\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 do agente (hobots.
|
|
1
|
+
{"version":3,"sources":["../src/protocol.ts","../protocol-limits.json","../src/wire-limits.ts"],"sourcesContent":["/**\n * Tipos do protocolo do Hobots (SDK → servidor).\n *\n * Este arquivo é a fonte de verdade do wire format: reexporta tipos (o app valida\n * os payloads com zod e trava o drift via `satisfies z.ZodType<...>` contra eles)\n * e os LIMITES numéricos do wire (WIRE_LIMITS, de protocol-limits.json) — o único\n * valor de runtime daqui, para a api aplicar exatamente os caps que o SDK aplica.\n */\n\nexport { WIRE_LIMITS, type WireLimits } from './wire-limits';\n\nimport type { EnvironmentInput } from './environment-protocol';\nimport type { MachineSnapshot } from './machine-protocol';\n\n// Wire format de execuções (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 RunStartRequest,\n RunStartResponse,\n RunError,\n RunProgress,\n RunUpdateRequest,\n RunUpdateResponse,\n RunItemStatus,\n RunItemInput,\n RunItemsCreateRequest,\n RunItemsCreateResponse,\n ItemColumn,\n ItemsDeclaration,\n InstanceMode,\n RunAttachmentContentType,\n RunAttachmentCreateRequest,\n RunAttachmentCreateResponse,\n RunAttachmentConfirmRequest,\n RunAttachmentConfirmResponse,\n FileStorage,\n FileRef,\n FileDownloadResponse,\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 do agente (as etapas de hobots.step dentro do 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","{\n \"MAX_ITEMS_PER_ENVELOPE\": 50,\n \"HEARTBEAT_DEFAULT_INTERVAL_MS\": 30000,\n \"HEARTBEAT_MIN_INTERVAL_MS\": 1000,\n \"HEARTBEAT_MAX_INTERVAL_MS\": 900000,\n \"MAX_CLAIM_PER_POLL\": 5,\n \"MAX_LOG_MESSAGE_CHARS\": 10000,\n \"MAX_LOGS_PER_PATCH\": 100,\n \"MAX_ERROR_MESSAGE_CHARS\": 10000,\n \"MAX_ERROR_STACK_CHARS\": 20000,\n \"MAX_ITEMS_PER_REQUEST\": 500,\n \"MAX_ITEM_MESSAGE_CHARS\": 2000,\n \"MAX_ITEM_PAYLOAD_BYTES\": 16384,\n \"MAX_ITEM_COLUMNS\": 12,\n \"MAX_ITEM_COLUMN_LABEL_CHARS\": 60,\n \"MAX_ATTACHMENT_BYTES\": 5242880,\n \"MAX_ATTACHMENTS_PER_RUN\": 20,\n \"MAX_ATTACHMENTS_PER_LOG_ENTRY\": 4,\n \"MAX_CAPTION_CHARS\": 200,\n \"MAX_IMAGE_DIMENSION_PX\": 20000\n}\n","import limits from '../protocol-limits.json';\n\n/**\n * Limites do wire SDK ↔ servidor — fonte ÚNICA (protocol-limits.json): o SDK\n * Node lê daqui, a @hobots/api importa de `hobots/protocol` e o SDK Python\n * confere a paridade num teste que lê o mesmo JSON. Mudar um limite = mudar o\n * JSON (e, no Python, a constante espelhada — o teste acusa).\n */\nexport const WIRE_LIMITS = limits as Readonly<typeof limits>;\nexport type WireLimits = typeof WIRE_LIMITS;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA,EACE,wBAA0B;AAAA,EAC1B,+BAAiC;AAAA,EACjC,2BAA6B;AAAA,EAC7B,2BAA6B;AAAA,EAC7B,oBAAsB;AAAA,EACtB,uBAAyB;AAAA,EACzB,oBAAsB;AAAA,EACtB,yBAA2B;AAAA,EAC3B,uBAAyB;AAAA,EACzB,uBAAyB;AAAA,EACzB,wBAA0B;AAAA,EAC1B,wBAA0B;AAAA,EAC1B,kBAAoB;AAAA,EACpB,6BAA+B;AAAA,EAC/B,sBAAwB;AAAA,EACxB,yBAA2B;AAAA,EAC3B,+BAAiC;AAAA,EACjC,mBAAqB;AAAA,EACrB,wBAA0B;AAC5B;;;ACZO,IAAM,cAAc;","names":[]}
|
package/dist/protocol.d.cts
CHANGED
|
@@ -146,7 +146,10 @@ interface RunDescriptor {
|
|
|
146
146
|
id: string;
|
|
147
147
|
name: string;
|
|
148
148
|
} | null;
|
|
149
|
-
/**
|
|
149
|
+
/**
|
|
150
|
+
* Origem da run: 'hobots' | 'link' | 'schedule' | 'agent' | 'local' (informativo; 'agent' = aberta pelo
|
|
151
|
+
* próprio SDK no modo start; 'local' = modo local, sem Central — nunca chega ao servidor)
|
|
152
|
+
*/
|
|
150
153
|
source: string;
|
|
151
154
|
/** ISO 8601 (relógio do servidor) */
|
|
152
155
|
created_at: string;
|
|
@@ -454,7 +457,7 @@ interface TransactionPayload {
|
|
|
454
457
|
tags?: Record<string, string>;
|
|
455
458
|
release?: string;
|
|
456
459
|
environment?: EnvironmentInput;
|
|
457
|
-
/** Correlaciona esta transaction a uma execução do agente (hobots.
|
|
460
|
+
/** Correlaciona esta transaction a uma execução do agente (as etapas de hobots.step dentro do handler) */
|
|
458
461
|
run_id?: string;
|
|
459
462
|
}
|
|
460
463
|
interface HeartbeatPayload {
|
package/dist/protocol.d.ts
CHANGED
|
@@ -146,7 +146,10 @@ interface RunDescriptor {
|
|
|
146
146
|
id: string;
|
|
147
147
|
name: string;
|
|
148
148
|
} | null;
|
|
149
|
-
/**
|
|
149
|
+
/**
|
|
150
|
+
* Origem da run: 'hobots' | 'link' | 'schedule' | 'agent' | 'local' (informativo; 'agent' = aberta pelo
|
|
151
|
+
* próprio SDK no modo start; 'local' = modo local, sem Central — nunca chega ao servidor)
|
|
152
|
+
*/
|
|
150
153
|
source: string;
|
|
151
154
|
/** ISO 8601 (relógio do servidor) */
|
|
152
155
|
created_at: string;
|
|
@@ -454,7 +457,7 @@ interface TransactionPayload {
|
|
|
454
457
|
tags?: Record<string, string>;
|
|
455
458
|
release?: string;
|
|
456
459
|
environment?: EnvironmentInput;
|
|
457
|
-
/** Correlaciona esta transaction a uma execução do agente (hobots.
|
|
460
|
+
/** Correlaciona esta transaction a uma execução do agente (as etapas de hobots.step dentro do handler) */
|
|
458
461
|
run_id?: string;
|
|
459
462
|
}
|
|
460
463
|
interface HeartbeatPayload {
|
package/docs/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
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 agente é a própria tarefa.** Não existe `init()` nem slug: a única chamada a `register()`/`start()` configura o processo (credenciais do ambiente, heartbeat sempre ligado, captura de erros não tratados) e diz o que o robô faz. O objeto global `hobots` expõe a execução atual — `hobots.log`, `hobots.items`, `hobots.progress`, `hobots.
|
|
5
|
+
**O agente é a própria tarefa.** Não existe `init()` nem slug: a única chamada a `register()`/`start()` configura o processo (credenciais do ambiente, heartbeat sempre ligado, captura de erros não tratados) e diz o que o robô faz. O objeto global `hobots` expõe a execução atual — `hobots.log`, `hobots.items`, `hobots.progress`, `hobots.step`… — de qualquer lugar do programa.
|
|
6
6
|
|
|
7
7
|
| Modo | Chamada | Para quê |
|
|
8
8
|
| --- | --- | --- |
|
|
@@ -15,7 +15,7 @@ Uma chamada por processo: um processo é um agente, e um agente faz uma coisa.
|
|
|
15
15
|
|
|
16
16
|
- [Getting started](./getting-started.md) — instalação, envs, o primeiro agente nos dois modos, encerramento.
|
|
17
17
|
- [Execuções](./runs.md) — handler, o objeto `hobots`, itens, progresso, anexos, cancelamento automático, retry, concorrência, idempotência.
|
|
18
|
-
- [Conceitos](./concepts.md) — o vocabulário: agente, execução, evento, issue,
|
|
18
|
+
- [Conceitos](./concepts.md) — o vocabulário: agente, execução, evento, issue, etapa, heartbeat, instância, claim.
|
|
19
19
|
- [Referência de API](./api-reference.md) — assinaturas, tabelas de opções e tempos.
|
|
20
20
|
- [Protocolo & transporte](./protocol.md) — Client Secret, endpoints (`/v1/ingest`, `/v1/runs/*`), envelope, retries, claim atômico.
|
|
21
21
|
|
package/docs/api-reference.md
CHANGED
|
@@ -18,8 +18,9 @@ ESM e CommonJS (`const { hobots } = require('hobots')`), com `.d.ts`. Depois do
|
|
|
18
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
19
|
| `hobots.captureException` | `(error: unknown, hint?: CaptureHint) => string \| undefined` | Issue no módulo Issues; devolve o `event_id`. Antes de iniciar o agente: descarta com aviso. |
|
|
20
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. |
|
|
21
|
-
| `hobots.
|
|
22
|
-
| `hobots.
|
|
21
|
+
| `hobots.step` | `<T>(name: string, fn: (etapa: StepHandle) => T \| Promise<T>, options?: StepOptions) => Promise<T>` | Registra a etapa `name` executando `fn`, com nova tentativa quando ela lança (`retries` default 1, `retryDelayMs` default 5_000). Resolve com o retorno de `fn`. Fora de uma execução: aviso único e roda `fn` com o mesmo retry, sem registrar nada. Ver [Execuções](./runs.md#etapas-hobotsstep). |
|
|
22
|
+
| `hobots.flush` | `(timeoutMs = 15_000) => Promise<void>` | Envia **agora** o que a execução atual acumulou: etapas raiz finalizadas viram transactions parciais (op `hobots.run.flush`), itens, logs e progresso pendentes saem sem esperar o throttle. Aguarda a entrega até `timeoutMs`. Nunca lança; fora de uma execução não faz nada. Feito para o modo start, a cada ciclo. |
|
|
23
|
+
| `hobots.image` | `(input: AttachInput, options?: ImageOptions) => ImageAttachment` | Embrulha um print para ir junto de um log, item ou etapa. Puro, nunca lança. |
|
|
23
24
|
| `isFileRef` / `hobots.isFileRef` | `(value: unknown) => value is FileRef` | Reconhece um arquivo de campo de upload nos params. Puro. |
|
|
24
25
|
|
|
25
26
|
## Contexto da execução (`RunContext` — os membros de `hobots` e o `ctx` do handler)
|
|
@@ -29,11 +30,11 @@ ESM e CommonJS (`const { hobots } = require('hobots')`), com `.d.ts`. Depois do
|
|
|
29
30
|
| `runId` | `string` | Identidade da execução (`''` fora de uma execução) |
|
|
30
31
|
| `params` | `Record<string, unknown>` | Params da solicitação (formulário do agente); `{}` no modo start |
|
|
31
32
|
| `log` | `RunLogger` — `debug/info/warn/error/success(message, ...rest)` | Nunca lança. Vários argumentos viram texto unidos por espaço (como `console.log`); um `hobots.image(...)` no **último** argumento vira o print da linha. Eco no terminal: `[hobots] INFO …` |
|
|
32
|
-
| `items` | `RunItems` — `add(item)`, `succeeded(payload?, opts?)`, `failed(message, payload?, opts?)`, `occurrence(message, payload?, opts?)`; `opts.id` = identidade de negócio, `opts.image` = print do item. `each(list, (element, item, index) => …, { id?, payload?, op?, description?, progress? }) => Promise<{ total, succeeded, failed, occurrence }>` processa a lista inteira: exceção no handler vira item `failed` +
|
|
33
|
+
| `items` | `RunItems` — `add(item)`, `succeeded(payload?, opts?)`, `failed(message, payload?, opts?)`, `occurrence(message, payload?, opts?)`; `opts.id` = identidade de negócio, `opts.image` = print do item. `each(list, (element, item, index) => …, { id?, payload?, op?, description?, progress? }) => Promise<{ total, succeeded, failed, occurrence }>` processa a lista inteira: exceção no handler vira item `failed` + etapa em erro e o loop segue; `item: RunItemHandle` = `succeeded(payload?, { image }?)/failed(msg, payload?, { image }?)/occurrence(…)`, `id`, `index`, `step` (`StepHandle` da etapa do item), `reported` | Ponto de parada cooperativo (o `each` para só em cancelamento/timeout) |
|
|
33
34
|
| `progress` | `RunProgressReporter` — `total(n)`, `advance(count = 1)`, `set(current, total?)`, `current`, `snapshot` | `total/advance/set` são pontos de parada |
|
|
34
35
|
| `files` | `RunFiles` — `list(): FileRef[]`, `get(field): FileRef \| FileRef[] \| undefined`, `download(refOrField: FileRef \| string, { dest? }?): Promise<string>`, `downloadAll(field, { dest? }?): Promise<string[]>`, `read(refOrField): Promise<Buffer>` | Arquivos do formulário; o download consome a origem (S3 confirmado/apagado, pasta do Studio removida). Cópias sem `dest` são apagadas no fim da execução. Respeita o `signal`. Ver [Execuções](./runs.md#arquivos-do-formulário-hobotsfiles) |
|
|
35
|
-
| `
|
|
36
|
-
| `
|
|
36
|
+
| `step(name, fn, options?)` | `<T>(name: string, fn: StepFn<T>, options?: { retries?: number; retryDelayMs?: RetryDelay }) => Promise<T>` — uma etapa no waterfall: abre, roda `fn(etapa)`, tenta de novo se lançar (`retries` default 1; 0 desliga), fecha. `warning` + `data.message` + `data.attempts` quando a nova tentativa deu certo; `error` + `data.error = { type, message }` quando esgotou (a exceção sobe); `cancelled` em cancelamento/timeout. `StepHandle` = `warning(message, { image }?)`, `image(png)`, `data(key, value)` (`attachments` reservada). Aninha automaticamente na etapa corrente. | Ponto de parada (lança `RunCanceled`) |
|
|
37
|
+
| `flush` | `(timeoutMs = 15_000) => Promise<void>` — envia já as etapas raiz finalizadas (transactions parciais, op `hobots.run.flush`) e os itens/logs/progresso pendentes | Nunca lança |
|
|
37
38
|
| `signal` | `AbortSignal` | Cancelamento (app, timeout, shutdown) |
|
|
38
39
|
| `throwIfCanceled()` | `() => void` | Lança `RunCanceled` se o signal disparou |
|
|
39
40
|
| `sleep(ms)` | `(ms: number) => Promise<void>` | Rejeita com `RunCanceled` no cancelamento |
|
|
@@ -51,7 +52,7 @@ Só há uma chamada por processo, então todas as opções são **do agente**. O
|
|
|
51
52
|
|
|
52
53
|
| Opção | Tipo | Default | Descrição |
|
|
53
54
|
| --- | --- | --- | --- |
|
|
54
|
-
| `clientSecret` | `string` | env `HOBOTS_CLIENT_SECRET` | `hb_<64 hex>`; ausente/inválido lança citando a env (sem ecoar o valor) |
|
|
55
|
+
| `clientSecret` | `string` | env `HOBOTS_CLIENT_SECRET` | `hb_<64 hex>`; ausente/inválido lança citando a env (sem ecoar o valor). Ignorado com `HOBOTS_LOCAL_RUN=1` ([modo local](./runs.md#modo-local-teste-sem-a-central)) |
|
|
55
56
|
| `instanceId` | `string` | env `HOBOTS_INSTANCE_ID` → `os.hostname()` | identidade estável da instância |
|
|
56
57
|
| `environment` | `'development' \| 'testing' \| 'staging' \| 'production' \| string` | `'production'` | vai em eventos, transactions e na instância; filtra alertas |
|
|
57
58
|
| `release` | `string` | — | versão do programa |
|
|
@@ -79,7 +80,20 @@ Só há uma chamada por processo, então todas as opções são **do agente**. O
|
|
|
79
80
|
| --- | --- | --- | --- |
|
|
80
81
|
| `concurrency` | `number` | `1` | execuções simultâneas do agente (1..5) |
|
|
81
82
|
| `pollIntervalMs` | `number` | `2_000` (mín. 1_000) | intervalo entre polls sem trabalho |
|
|
82
|
-
| `runId` | `string` | env `HOBOTS_RUN_ID` | avançado: executa só esta run (já reivindicada para a instância, ex.: pelo Studio) em vez de fazer poll — `POST /v1/runs/{id}/start`, handler uma vez e `close()`; 404/409 encerram sem rodar o handler |
|
|
83
|
+
| `runId` | `string` | env `HOBOTS_RUN_ID` | avançado: executa só esta run (já reivindicada para a instância, ex.: pelo Studio) em vez de fazer poll — `POST /v1/runs/{id}/start`, handler uma vez e `close()`; 404/409 encerram sem rodar o handler. Ignorado no modo local |
|
|
84
|
+
|
|
85
|
+
### Envs sem opção equivalente
|
|
86
|
+
|
|
87
|
+
| Env | Efeito |
|
|
88
|
+
| --- | --- |
|
|
89
|
+
| `HOBOTS_API_URL` | só em desenvolvimento: outro host da API |
|
|
90
|
+
| `HOBOTS_LOCAL_RUN` | `1` = modo local (teste sem a Central): handler uma vez, sem rede, exit code pelo desfecho — ver [Execuções](./runs.md#modo-local-teste-sem-a-central) |
|
|
91
|
+
| `HOBOTS_RUN_PARAMS` | modo local: objeto JSON dos params (ausente = `{}`); inválido → stderr + exit 2 |
|
|
92
|
+
| `HOBOTS_LOCAL_FILES_DIR` | pasta dos arquivos `storage: 'local'` deixados pelo Studio (`<dir>/<uuid>/<nome>`) |
|
|
93
|
+
|
|
94
|
+
### CLI
|
|
95
|
+
|
|
96
|
+
`npx hobots run <entry.js> [--params <arquivo.json>] [--param <chave>=<valor>]... [--file <campo>=<caminho>]... [--reset-ledger] [-- <args do bot>]` — roda o bot no modo local com o mesmo Node; exit code do bot (2 em uso errado). Detalhes em [Execuções → Modo local](./runs.md#a-cli-hobots-run).
|
|
83
97
|
|
|
84
98
|
`StartOptions = RunOptions`.
|
|
85
99
|
|
package/docs/concepts.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Conceitos
|
|
2
2
|
|
|
3
|
-
Esta página explica **o que cada coisa é** — o vocabulário que as outras páginas usam: agente, execução, item, evento, issue,
|
|
3
|
+
Esta página explica **o que cada coisa é** — o vocabulário que as outras páginas usam: agente, execução, item, evento, issue, etapa, heartbeat, instância, claim. As outras páginas mostram **como usar**; aqui está o **por que existe**.
|
|
4
4
|
|
|
5
5
|
Defaults numéricos vivem em [Tempos e intervalos](./api-reference.md#tempos-e-intervalos) e nas [tabelas de opções](./api-reference.md#tabelas-de-opções).
|
|
6
6
|
|
|
@@ -15,11 +15,11 @@ Defaults numéricos vivem em [Tempos e intervalos](./api-reference.md#tempos-e-i
|
|
|
15
15
|
| [Item](#item) | Uma unidade de trabalho entregue | Cada nota, boleto, arquivo processado |
|
|
16
16
|
| [Progresso](#progresso) | Quantos de quantos | Barra e tempo restante no app |
|
|
17
17
|
| [Log da execução](#log-da-execução) | Narração do que a execução fez | `hobots.log` — nunca lança |
|
|
18
|
-
| [Anexo](#anexo) | Um print pendurado num log, item ou
|
|
19
|
-
| [Cancelamento cooperativo](#cancelamento-cooperativo) | O app pede, o handler para no próximo ponto de parada | Automático em items/progress/
|
|
18
|
+
| [Anexo](#anexo) | Um print pendurado num log, item ou etapa | "Como estava a tela?" |
|
|
19
|
+
| [Cancelamento cooperativo](#cancelamento-cooperativo) | O app pede, o handler para no próximo ponto de parada | Automático em items/progress/step/sleep/retry |
|
|
20
20
|
| [Retry](#retry) | Tentar de novo na mesma execução | Instabilidade passageira |
|
|
21
21
|
| [Evento e issue](#evento-e-issue) | A foto de um erro, agrupada em issue | `hobots.captureException` e erros não tratados |
|
|
22
|
-
| [
|
|
22
|
+
| [Etapa](#etapa) (e a [transaction](#transaction) por trás) | Quanto demorou e onde o tempo foi — com nova tentativa | `hobots.step` |
|
|
23
23
|
| [Heartbeat](#heartbeat) | Batimento "estou vivo", sempre ligado | Saber que o robô está de pé |
|
|
24
24
|
| [instanceId](#instanceid) | A identidade da instância do robô | Env `HOBOTS_INSTANCE_ID` |
|
|
25
25
|
| [Release e environment](#release-e-environment) | Qual versão, em qual ambiente | Separar produção de homologação |
|
|
@@ -35,7 +35,7 @@ Defaults numéricos vivem em [Tempos e intervalos](./api-reference.md#tempos-e-i
|
|
|
35
35
|
| **Instância** — o par `(agente, instanceId)`; heartbeat e execuções convergem nela, com o modo (sob demanda / sempre ligado) | Módulo **Instâncias** (uptime, recursos da máquina, performance) |
|
|
36
36
|
| **Execução** — cada solicitação atendida por `register()`, ou a execução que `start()` abre sozinho | Módulo **Trabalhos** |
|
|
37
37
|
| **Evento** agrupado em **issue** — `captureException` e erros não tratados | Módulo **Issues** |
|
|
38
|
-
| **
|
|
38
|
+
| **Etapas** — `hobots.step` | Aba de performance da execução (waterfall) |
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
|
@@ -75,11 +75,11 @@ O **log da execução** narra o que aconteceu, em linhas com nível (`debug`, `i
|
|
|
75
75
|
|
|
76
76
|
## Anexo
|
|
77
77
|
|
|
78
|
-
Um **anexo** é uma imagem — na prática, um print — vinculada ao evento que ela ilustra: uma linha de log, um item ou uma
|
|
78
|
+
Um **anexo** é uma imagem — na prática, um print — vinculada ao evento que ela ilustra: uma linha de log, um item ou uma etapa. Responde "como estava a tela?" quando texto não basta. Quem captura é o seu código (Playwright, Puppeteer, um arquivo); o SDK sobe direto para o S3 por URL presignada, em segundo plano, sem atrasar o log. Enquanto sobe, o app mostra "enviando imagem…".
|
|
79
79
|
|
|
80
80
|
## Cancelamento cooperativo
|
|
81
81
|
|
|
82
|
-
Cancelar no app **não mata** o handler — o SDK não tem como interromper código arbitrário com segurança (um navegador aberto, uma transação pela metade). O que ele faz: detecta o cancelamento (o PATCH periódico recebe 409), dispara `hobots.signal` e faz o próximo **ponto de parada** lançar `RunCanceled`. Os pontos de parada são as fronteiras naturais de trabalho: registrar um item, avançar o progresso, abrir uma
|
|
82
|
+
Cancelar no app **não mata** o handler — o SDK não tem como interromper código arbitrário com segurança (um navegador aberto, uma transação pela metade). O que ele faz: detecta o cancelamento (o PATCH periódico recebe 409), dispara `hobots.signal` e faz o próximo **ponto de parada** lançar `RunCanceled`. Os pontos de parada são as fronteiras naturais de trabalho: registrar um item, avançar o progresso, abrir uma etapa, dormir, tentar de novo. Por isso, num loop comum, o cancelamento funciona sem nenhuma linha extra. `hobots.log` nunca é ponto de parada. Se o handler ignorar o signal por tempo demais (`cancelGraceMs`), o SDK libera o slot e o handler vira um zumbi cujos reportes são descartados.
|
|
83
83
|
|
|
84
84
|
## Retry
|
|
85
85
|
|
|
@@ -89,13 +89,13 @@ Uma falha do handler costuma ser passageira (portal instável, rede). O SDK **te
|
|
|
89
89
|
|
|
90
90
|
Um **evento** é a foto de um erro: tipo, mensagem, stack trace, tags, ambiente, release e como estava a máquina. `hobots.captureException(erro)` cria um; erros não tratados (`uncaughtException`, `unhandledRejection`) também. Eventos parecidos são agrupados em uma **issue** pelo servidor (por stack trace, ou pelo `fingerprint` que você definir). O `type` diz o que ele é: `fatal`, `error` e `warning` abrem issue e podem alertar; `success` é registrado e nunca alerta. Log não é evento — narração fica no log da execução.
|
|
91
91
|
|
|
92
|
-
##
|
|
92
|
+
## Etapa
|
|
93
93
|
|
|
94
|
-
Uma **
|
|
94
|
+
Uma **etapa** é um trecho medido da execução com nome de negócio — "Entrar no portal", "Buscar pedido 123". A execução diz que levou 4 minutos; as etapas dizem que 3min40 foram esperando o portal. `hobots.step(nome, fn)` é a única chamada: o SDK abre a etapa, roda `fn`, **tenta de novo** se ela lançar (1 tentativa extra por padrão; `retries: 0` desliga) e fecha. Uma etapa só na Central cobre todas as tentativas: `warning` com o motivo quando a nova tentativa deu certo (e `attempts`), `error` quando esgotou (a exceção sobe), `cancelled` em cancelamento/timeout. `hobots.step` dentro de outra etapa vira sub-etapa; dentro do handler de `items.each`, sub-etapa da etapa do item. A função recebe o handle `etapa` para o que o SDK não vê sozinho: `warning(motivo)`, `image(png)`, `data(chave, valor)`. No app isso vira o **waterfall** da execução. Abrir uma etapa é um ponto de parada cooperativo. A etapa não entra no aviso de erro no topo do trabalho — ela aparece na aba de performance.
|
|
95
95
|
|
|
96
|
-
##
|
|
96
|
+
## Transaction
|
|
97
97
|
|
|
98
|
-
Uma **
|
|
98
|
+
Uma **transaction** é o envelope do wire que carrega as etapas: cada execução tem exatamente uma, chamada `run` (op `hobots.run`), aberta e finalizada pelo SDK e correlacionada à execução pelo `run_id`. Você não a vê nem cria — só registra etapas com `hobots.step`. Num agente sempre ligado, `hobots.flush()` envia as etapas raiz já terminadas como transactions parciais (op `hobots.run.flush`).
|
|
99
99
|
|
|
100
100
|
## Heartbeat
|
|
101
101
|
|
package/docs/getting-started.md
CHANGED
|
@@ -76,4 +76,4 @@ O robô abre a própria execução ao iniciar e ela aparece em **Trabalhos** com
|
|
|
76
76
|
|
|
77
77
|
`hobots.close(timeoutMs?)` para o polling, espera as execuções em voo, para o heartbeat e faz o flush final. Sem handler próprio de `SIGINT`/`SIGTERM`, o SDK reporta as execuções ativas como `AgentTerminated` antes do processo morrer.
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
A seguir: [Execuções](./runs.md) para etapas, itens, progresso, anexos, cancelamento e retry; [Referência de API](./api-reference.md) para todas as opções.
|
package/docs/protocol.md
CHANGED
|
@@ -38,7 +38,7 @@ Endpoint fixo `POST /v1/ingest` recebe **envelopes** JSON autenticados por Beare
|
|
|
38
38
|
|
|
39
39
|
Tipos de item: `event`, `transaction`, `heartbeat` (definidos em `hobots/protocol`).
|
|
40
40
|
|
|
41
|
-
Todo `transaction` carrega `run_id`: é assim que a [transaction da execução](./concepts.md#transaction) (`hobots.
|
|
41
|
+
Todo `transaction` carrega `run_id`: é assim que a [transaction da execução](./concepts.md#transaction) (aberta pelo SDK; as etapas vêm de `hobots.step`) é correlacionada, e o que permite o app montar o waterfall de performance dentro da solicitação. Cada etapa (`StepData`) vai sem `op`, com `description` = nome (recortado em 2048 no cliente) e, quando houve mais de uma tentativa, `data.attempts`.
|
|
42
42
|
|
|
43
43
|
`items` aceita de 1 a **50** itens por envelope — acima disso a API responde `422`. O `maxBatchSize` é só o gatilho do flush: quando a fila tem mais que esse teto (burst de captures, ou fila acumulada durante uma janela de rede ruim), o transport a fatia em lotes de 50 e faz um POST por lote.
|
|
44
44
|
|
|
@@ -165,7 +165,7 @@ Corpo `RunItemsCreateRequest`:
|
|
|
165
165
|
|
|
166
166
|
Resposta `200` `RunItemsCreateResponse`: `{ "accepted": 1, "duplicates": 0 }` — `duplicates` conta os colapsados por `(run, id)` repetido no mesmo lote (vale o último reporte; no banco é upsert).
|
|
167
167
|
|
|
168
|
-
- `attachments` é **opcional** e aditivo (uma API anterior ao recurso descarta o campo). As
|
|
168
|
+
- `attachments` é **opcional** e aditivo (uma API anterior ao recurso descarta o campo). As etapas carregam o print do mesmo jeito, em `data.attachments` do `StepData` que vai no `POST /v1/ingest` da transaction — sem campo novo no wire.
|
|
169
169
|
|
|
170
170
|
- **Run cancelada/terminal aceita itens** (200): item é fato de trabalho já feito — mesmo racional do log tardio. Esta rota não tem 409.
|
|
171
171
|
- 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.
|
|
@@ -187,11 +187,11 @@ Corpo `RunAttachmentCreateRequest`:
|
|
|
187
187
|
|
|
188
188
|
```jsonc
|
|
189
189
|
{
|
|
190
|
-
"id": "<32 hex>", // gerado pelo agente, já referenciado no log/item/
|
|
190
|
+
"id": "<32 hex>", // gerado pelo agente, já referenciado no log/item/etapa
|
|
191
191
|
"content_type": "image/png", // 'image/png' | 'image/jpeg' | 'image/webp'
|
|
192
192
|
"size_bytes": 184320, // exato — entra na assinatura da URL
|
|
193
193
|
"caption": "portal após o login", // opcional, máx. 500
|
|
194
|
-
"log_ts": "…", // opcional, ISO 8601 — ts do evento (log/item/
|
|
194
|
+
"log_ts": "…", // opcional, ISO 8601 — ts do evento (log/item/etapa)
|
|
195
195
|
"width": 1920, "height": 1080 // opcionais
|
|
196
196
|
}
|
|
197
197
|
```
|
|
@@ -226,7 +226,7 @@ Confirma o upload. Corpo `RunAttachmentConfirmRequest`: `{ "status": "uploaded"
|
|
|
226
226
|
|
|
227
227
|
É **conforto de UX**, não correção: enquanto não confirmado, o app mostra a miniatura como "enviando imagem…". O agente engole falhas desta chamada.
|
|
228
228
|
|
|
229
|
-
Limites de anexo (aplicados no SDK **e** no servidor): PNG/JPEG/WEBP, 5 MB por imagem, 20 por execução, 4 por evento (linha de log, item ou
|
|
229
|
+
Limites de anexo (aplicados no SDK **e** no servidor): PNG/JPEG/WEBP, 5 MB por imagem, 20 por execução, 4 por evento (linha de log, item ou etapa).
|
|
230
230
|
|
|
231
231
|
### `POST /v1/runs/{id}/files/{fileId}/download`
|
|
232
232
|
|
package/docs/runs.md
CHANGED
|
@@ -29,9 +29,11 @@ Dentro do handler — e em **qualquer módulo** chamado por ele — o objeto glo
|
|
|
29
29
|
```js
|
|
30
30
|
// notas.js — não recebe ctx nenhum
|
|
31
31
|
export async function baixar(nota) {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
const xml = await hobots.step(`GET /notas/${nota}`, async etapa => {
|
|
33
|
+
const xml = await portal.baixar(nota); // lançou? o SDK tenta de novo uma vez
|
|
34
|
+
etapa.data('bytes', xml.length);
|
|
35
|
+
return xml;
|
|
36
|
+
});
|
|
35
37
|
hobots.items.succeeded({ numero_nota: nota }, { id: nota });
|
|
36
38
|
}
|
|
37
39
|
```
|
|
@@ -43,7 +45,7 @@ Isso funciona com `concurrency > 1` porque cada execução roda no seu contexto
|
|
|
43
45
|
| `hobots.log.<debug\|info\|warn\|error\|success>(msg, ...rest)` | Enviado incrementalmente (PATCHes com throttle) e ecoado no terminal (`[hobots] INFO …`). Nunca lança. `hobots.image(...)` no último argumento = print da linha. |
|
|
44
46
|
| `hobots.items` | Ver [Itens](#itens-de-execução-hobotsitems) |
|
|
45
47
|
| `hobots.progress` | Ver [Progresso](#progresso-hobotsprogress) |
|
|
46
|
-
| `hobots.
|
|
48
|
+
| `hobots.step(nome, fn, opts?)` | Ver [Etapas](#etapas-hobotsstep) |
|
|
47
49
|
| `hobots.image(imagem, opts?)` | Ver [Imagens](#imagens-screenshots) |
|
|
48
50
|
| `hobots.captureException(erro, hint?)` | Issue no módulo Issues; `hint.attachment` anexa um print à linha de log |
|
|
49
51
|
| `hobots.retry(fn, opts?)` | Ver [Retry](#retry) |
|
|
@@ -52,6 +54,7 @@ Isso funciona com `concurrency > 1` porque cada execução roda no seu contexto
|
|
|
52
54
|
|
|
53
55
|
- **Retorno** do handler: **ignorado** (resultados podem conter dados sensíveis). Reporte por logs e itens.
|
|
54
56
|
- **Erro lançado**: nova tentativa (ver Retry) e, esgotadas, `failed` com tipo, mensagem (cap 10k) e stack (cap 20k), passando pelo scrub de segredos.
|
|
57
|
+
- Para rodar o handler na sua máquina sem a Central, ver [Modo local](#modo-local-teste-sem-a-central).
|
|
55
58
|
|
|
56
59
|
> **Dados sensíveis:** logs, itens, erros e prints são enviados ao servidor e ficam visíveis no app.
|
|
57
60
|
|
|
@@ -79,7 +82,7 @@ O contrato da plataforma — **todo item previsto termina com um desfecho regist
|
|
|
79
82
|
const resultado = await hobots.items.each(
|
|
80
83
|
contas,
|
|
81
84
|
async (conta, item) => {
|
|
82
|
-
await login(conta); // lançou → item `failed` com a mensagem,
|
|
85
|
+
await login(conta); // lançou → item `failed` com a mensagem, etapa em erro, segue
|
|
83
86
|
const fatura = await baixar(conta);
|
|
84
87
|
if (!fatura) return item.occurrence('sem fatura no mês'); // ocorrência de negócio
|
|
85
88
|
item.succeeded({ fatura: fatura.numero, valor: fatura.valor }); // mescla com o payload base
|
|
@@ -90,8 +93,8 @@ const resultado = await hobots.items.each(
|
|
|
90
93
|
// resultado = { total, succeeded, failed, occurrence }
|
|
91
94
|
```
|
|
92
95
|
|
|
93
|
-
- Cada elemento vira uma **
|
|
94
|
-
- **Exceção no handler** = item `failed` com a mensagem do erro, `
|
|
96
|
+
- Cada elemento vira uma **etapa** no waterfall (`op` 'item', descrição = `id`; `hobots.step(...)` dentro do handler vira sub-etapa dela, e `item.step` é o handle `warning`/`image`/`data`) e um **item** com o `id` e o payload base.
|
|
97
|
+
- **Exceção no handler** = item `failed` com a mensagem do erro, etapa em `error`, linha de erro no log da run — e o loop **segue para o próximo**. Nada do que o handler já registrou (`item.failed/occurrence`) é sobrescrito. O `each` não repete o item: para tentar de novo um trecho, use `hobots.step` dentro do handler.
|
|
95
98
|
- **Progresso automático**: declara o total pelo tamanho da lista (se o bot ainda não declarou um) e avança a cada elemento, inclusive nos que falharam. `progress: false` deixa o progresso por conta do bot.
|
|
96
99
|
- Só **cancelamento, timeout e shutdown** interrompem o loop: o `RunCanceled` sobe, o item em curso não é registrado e o progresso não avança.
|
|
97
100
|
- Aceita `Iterable` ou `AsyncIterable` (sem `length`, nenhum total é declarado).
|
|
@@ -112,11 +115,41 @@ hobots.register(handler, {
|
|
|
112
115
|
- A declaração vai na presença da instância (`instance.items` no poll / abertura de run). No app, **Configurações → agente → itens da execução** pode personalizar as colunas (e restaurar as do código).
|
|
113
116
|
- Sem declaração nem personalização, o app mostra as chaves encontradas nos próprios payloads.
|
|
114
117
|
|
|
118
|
+
## Etapas (`hobots.step`)
|
|
119
|
+
|
|
120
|
+
Uma **etapa** é um trecho da execução com nome de negócio, medido no waterfall da aba de performance. Uma chamada só: o SDK abre a etapa, roda a função, **tenta de novo** se ela lançar e fecha.
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
// retries = 1 e retryDelayMs = 5_000 por padrão
|
|
124
|
+
const pedido = await hobots.step('Buscar pedido', () => portal.buscar(numero));
|
|
125
|
+
|
|
126
|
+
// etapa não idempotente (enviar, pagar, emitir): sem repetir
|
|
127
|
+
await hobots.step(`Emitir nota ${numero}`, () => portal.emitir(numero), { retries: 0 });
|
|
128
|
+
|
|
129
|
+
// mais tentativas, espera própria (número ou função da tentativa, 1-based)
|
|
130
|
+
await hobots.step('Entrar no portal', login, { retries: 3, retryDelayMs: attempt => attempt * 2_000 });
|
|
131
|
+
|
|
132
|
+
// a função recebe o handle `etapa` para o que o SDK não vê sozinho
|
|
133
|
+
await hobots.step('Conferir anexos', async etapa => {
|
|
134
|
+
const anexos = await portal.anexos(numero);
|
|
135
|
+
etapa.data('total', anexos.length);
|
|
136
|
+
if (anexos.length === 0) etapa.warning('pedido sem anexos', { image: await page.screenshot() });
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
- **Uma etapa só na Central**, cobrindo todas as tentativas: `data.attempts` quando houve mais de uma; status `warning` com o motivo (`data.message = "tentativa 1/2 falhou: Tipo: mensagem"`) quando a nova tentativa deu certo — a menos que a função já tenha marcado `warning`; `error` com `data.error = { type, message }` quando esgotou (a exceção original sobe); `cancelled` em cancelamento, timeout ou shutdown (nunca repetem).
|
|
141
|
+
- Cada tentativa falhada vai para o **log da run**: `etapa "Buscar pedido": tentativa 1/2 falhou: Error: portal caiu — nova tentativa em 5000ms`. A espera é cancelável: uma execução cancelada no app não espera a próxima tentativa.
|
|
142
|
+
- **Aninhamento automático**: `hobots.step` dentro da função vira sub-etapa da etapa corrente — inclusive dentro do handler de `items.each`, onde aninha na etapa do item.
|
|
143
|
+
- O handle `etapa` (`StepHandle`): `warning(motivo, { image }?)` (uma etapa com erro não volta para aviso), `image(png)` (até 4 por etapa; vai em `data.attachments`), `data(chave, valor)` (`attachments` é reservada). Nada de abrir, fechar ou mudar status — isso é do SDK.
|
|
144
|
+
- Abrir a etapa é um **ponto de parada** (`RunCanceled` se a execução foi cancelada). O nome é recortado em 2048 caracteres no cliente.
|
|
145
|
+
- Fora de uma execução (aviso único), `hobots.step` roda a função com o mesmo retry, sem registrar nada. No [modo local](#modo-local-teste-sem-a-central) a etapa fica só na memória e o FINISH ecoa no terminal: `[hobots:step] FINISH Buscar pedido (812ms): tentativa 1/2 falhou: …`.
|
|
146
|
+
- `hobots.retry(fn)` continua existindo para repetir **sem etapa**; `retries` de `register()`/`start()` repete o **trabalho inteiro**.
|
|
147
|
+
|
|
115
148
|
## Entregar enquanto processa (`hobots.flush()`)
|
|
116
149
|
|
|
117
150
|
A transaction da execução só é enviada quando a run termina — num agente sempre ligado (`start()`) isso é nunca. `hobots.flush()` deixa o **bot** decidir quando entregar o que já fez:
|
|
118
151
|
|
|
119
|
-
- cada
|
|
152
|
+
- cada etapa **raiz** já finalizada (com todas as sub-etapas; as ainda abertas são fechadas) vai numa transaction parcial da mesma run (`op` `hobots.run.flush`, `event_id` novo, até 200 etapas cada) e sai da memória do SDK; etapas raiz ainda abertas ficam para o próximo flush;
|
|
120
153
|
- itens, logs e progresso pendentes saem já, sem esperar o throttle;
|
|
121
154
|
- aguarda a entrega com teto (`timeoutMs`, default 15 s) e **nunca lança** — pode ir num `finally`.
|
|
122
155
|
|
|
@@ -125,11 +158,11 @@ hobots.start(async () => {
|
|
|
125
158
|
while (true) {
|
|
126
159
|
const lote = await fila.pegar();
|
|
127
160
|
if (lote.length) {
|
|
128
|
-
const rodada = hobots.transaction.start('rodada', `${lote.length} pedido(s)`);
|
|
129
161
|
try {
|
|
130
|
-
|
|
162
|
+
await hobots.step(`rodada: ${lote.length} pedido(s)`, async () => {
|
|
163
|
+
for (const pedido of lote) await processar(pedido); // sub-etapas (hobots.step) + hobots.items.*
|
|
164
|
+
}, { retries: 0 });
|
|
131
165
|
} finally {
|
|
132
|
-
rodada.succeeded();
|
|
133
166
|
await hobots.flush(); // waterfall e itens desta rodada já na Central
|
|
134
167
|
}
|
|
135
168
|
}
|
|
@@ -138,7 +171,7 @@ hobots.start(async () => {
|
|
|
138
171
|
});
|
|
139
172
|
```
|
|
140
173
|
|
|
141
|
-
Fila vazia → não abra
|
|
174
|
+
Fila vazia → não abra etapa: cada etapa também vira duas linhas (START/FINISH) no log da execução, que guarda no máximo 1000.
|
|
142
175
|
|
|
143
176
|
## Progresso (`hobots.progress`)
|
|
144
177
|
|
|
@@ -182,9 +215,54 @@ for (const ref of hobots.files.list()) hobots.log.info(ref.name, ref.size);
|
|
|
182
215
|
|
|
183
216
|
`isFileRef(valor)` (export do pacote, também `hobots.isFileRef`) reconhece a forma de um `FileRef` — útil para percorrer params aninhados.
|
|
184
217
|
|
|
218
|
+
## Modo local (teste sem a Central)
|
|
219
|
+
|
|
220
|
+
Para testar o robô na sua máquina sem Client Secret, sem Central e sem rede, rode-o com a env **`HOBOTS_LOCAL_RUN=1`**. `register()` e `start()` se comportam igual neste modo: o handler roda **uma vez**, com `params = JSON.parse(HOBOTS_RUN_PARAMS || '{}')`, e o processo encerra. O código do bot não muda — `hobots.log`, `hobots.items`, `hobots.progress`, `hobots.files`, `items.each`, `hobots.step` (o FINISH de cada etapa ecoa como `[hobots:step] FINISH <nome> (123ms): <motivo>`), cancelamento e retry são os mesmos de produção; só o transporte é trocado por um que não fala com ninguém.
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
HOBOTS_LOCAL_RUN=1 HOBOTS_RUN_PARAMS='{"lote": 3}' node bot.js
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
- `HOBOTS_CLIENT_SECRET`, `HOBOTS_RUN_ID` e `HOBOTS_API_URL` são ignoradas. Nenhuma requisição sai: sem heartbeat, sem poll, sem issues (`hobots.captureException` devolve `undefined`).
|
|
227
|
+
- `hobots.runId` é `'local'`; `HOBOTS_RUN_PARAMS` que não seja um objeto JSON válido imprime `[hobots] ERROR HOBOTS_RUN_PARAMS inválido: <motivo>` no stderr e encerra com código **2**.
|
|
228
|
+
- **Exit code** pelo desfecho: **0** handler terminou; **1** lançou (depois dos retries configurados) — no stderr sai `[hobots] ERROR execução falhou — <Tipo>: <mensagem>` e a stack completa; **130** cancelado — `SIGINT`/`SIGTERM` disparam `hobots.signal` na execução em voo e esperam o handler parar por até 4 s (com `installExitHandlers: false`, ou com handler próprio do programa para o sinal, o SDK não intervém).
|
|
229
|
+
|
|
230
|
+
O que sai no **stdout** (linhas estáveis, pensadas para quem lê a saída por script):
|
|
231
|
+
|
|
232
|
+
| Linha | Quando |
|
|
233
|
+
| --- | --- |
|
|
234
|
+
| `[hobots] INFO modo local — execução sem a Central; params de HOBOTS_RUN_PARAMS` | primeira linha, ao entrar no modo |
|
|
235
|
+
| `[hobots] INFO <mensagem>` (e `WARNING`/`ERROR` no stderr) | cada `hobots.log.*`, como sempre |
|
|
236
|
+
| `[hobots:progress] <current>/<total>` | `hobots.progress` (também fora do modo local) |
|
|
237
|
+
| `[hobots:item] <status>\t<id>\t<message>` | um item por linha, `status` = `succeeded` \| `occurrence` \| `failed`; tab e quebra de linha em id/mensagem viram espaço |
|
|
238
|
+
| `[hobots:summary] total=<n> ok=<n> avisos=<n> falhas=<n> pulados=0` | no fim, contagem dos itens por status |
|
|
239
|
+
| `[hobots] INFO execução local concluída (<succeeded\|failed\|canceled>) em <N>s` | última linha |
|
|
240
|
+
|
|
241
|
+
Imagens e anexos (`hobots.image`, `{ image }` em itens e steps) são **descartados**, com um aviso único: `imagens e anexos não são enviados no modo local (descartados)`.
|
|
242
|
+
|
|
243
|
+
**Arquivos.** `hobots.files` funciona com `storage: 'local'`; o formato `$HOBOTS_LOCAL_FILES_DIR/<id>/<nome>` continua valendo, e só no modo local o `FileRef` aceita a chave extra **`path`** (caminho absoluto ou relativo à pasta de trabalho): `download` copia de `path` **sem apagar a origem** (o teste roda quantas vezes quiser) e sem conferir `size`. `path` inexistente ou que não é arquivo regular: `arquivo "<nome>" não encontrado em <path>`. Um arquivo enviado pela Central (`storage: 's3'`) não existe aqui: `arquivo "<nome>" enviado pela Central não está disponível no modo local`. Fora do modo local a chave `path` é ignorada.
|
|
244
|
+
|
|
245
|
+
### A CLI `hobots run`
|
|
246
|
+
|
|
247
|
+
A CLI monta as envs e os params por você — e roda o entry com o **mesmo Node** (`process.execPath`), na mesma pasta de trabalho, herdando o terminal; o exit code do bot é o da CLI:
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
npx hobots run bot.js --params entrada.json --param lote=3 --param nome='"ACME"' --file planilha=./dados.xlsx -- --verbose
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
| Opção | Efeito |
|
|
254
|
+
| --- | --- |
|
|
255
|
+
| `--params <arquivo.json>` | objeto JSON base dos params |
|
|
256
|
+
| `--param <chave>=<valor>` (repetível) | valor parseado como JSON quando for JSON válido (`3`, `true`, `"x"`, `[1]`), senão string crua; sobrescreve `--params` |
|
|
257
|
+
| `--file <campo>=<caminho>` (repetível) | vira um `FileRef` `{ id: <uuid>, name, size, contentType, storage: 'local', path: <caminho absoluto> }`; o mesmo campo repetido vira uma lista; `contentType` pela extensão (`xlsx`, `xls`, `csv`, `ods`, `pdf`, `json`, `txt`, `xml`, `png`, `jpg`/`jpeg`, `zip`; demais `application/octet-stream`); caminho inexistente → `arquivo não encontrado: <caminho>`, exit 2 |
|
|
258
|
+
| `--reset-ledger` | define `HOBOTS_LEDGER_RESET=1` no ambiente do bot (o ledger é do esqueleto do bot, não do SDK) |
|
|
259
|
+
| `-- <args>` | tudo depois de `--` vai para o bot |
|
|
260
|
+
|
|
261
|
+
`--help`/`-h` imprime a linha de uso no stdout (exit 0). Uso errado (sem entry, flag desconhecida, `--param` sem `=`): mensagem curta + linha de uso no stderr e exit **2**; entry inexistente → `arquivo não encontrado: <entry>`; `--params` com arquivo inexistente, JSON inválido ou que não é objeto → `--params: <motivo>` (exit 2). A CLI repassa `SIGINT`/`SIGTERM` ao bot e espera ele encerrar. O SDK Python tem a mesma CLI: `python -m hobots run bot.py …`.
|
|
262
|
+
|
|
185
263
|
## Imagens (screenshots)
|
|
186
264
|
|
|
187
|
-
Um print vai **junto do evento** que ele ilustra — a linha de log, o item ou a
|
|
265
|
+
Um print vai **junto do evento** que ele ilustra — a linha de log, o item ou a etapa — para o
|
|
188
266
|
usuário ver o estado do robô no momento em que aquilo aconteceu (botão de imagem no app):
|
|
189
267
|
|
|
190
268
|
```js
|
|
@@ -199,16 +277,12 @@ await hobots.items.each(notas, async (nota, item) => {
|
|
|
199
277
|
item.occurrence('sem fatura no mês', undefined, { image: await page.screenshot() });
|
|
200
278
|
});
|
|
201
279
|
|
|
202
|
-
// na
|
|
203
|
-
|
|
204
|
-
try {
|
|
280
|
+
// na etapa
|
|
281
|
+
await hobots.step(`emitir ${numero}`, async etapa => {
|
|
205
282
|
await emitir(numero);
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
} finally {
|
|
210
|
-
step.succeeded();
|
|
211
|
-
}
|
|
283
|
+
etapa.image(await page.screenshot()); // print do estado da tela
|
|
284
|
+
if (await page.isVisible('.aviso')) etapa.warning('portal demorou a responder', { image: await page.screenshot() });
|
|
285
|
+
}, { retries: 0 });
|
|
212
286
|
|
|
213
287
|
// numa issue (módulo Issues) — o print vai na linha de log que espelha o erro
|
|
214
288
|
hobots.captureException(erro, { attachment: hobots.image(await page.screenshot(), { caption: 'tela no momento do erro' }) });
|
|
@@ -220,7 +294,7 @@ O SDK não tira o print: aceita bytes (`Uint8Array`/`ArrayBuffer`/`Buffer`) ou u
|
|
|
220
294
|
|
|
221
295
|
Cancelar o trabalho no app **não mata** o handler: o SDK detecta (o PATCH periódico recebe 409), dispara `hobots.signal` e o próximo **ponto de parada** lança `RunCanceled`:
|
|
222
296
|
|
|
223
|
-
- `hobots.items.*`, `hobots.progress.total/advance/set`, `hobots.
|
|
297
|
+
- `hobots.items.*`, `hobots.progress.total/advance/set`, `hobots.step()`, `hobots.sleep()` e cada tentativa de `hobots.retry()`.
|
|
224
298
|
- **Nunca `hobots.log.*`** — log em `finally` de limpeza não pode explodir.
|
|
225
299
|
|
|
226
300
|
```js
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hobots",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "SDK JavaScript da Hobots — o agente é a própria tarefa: register() atende solicitações da plataforma (sob demanda), start() roda sempre ligado e reporta logs, itens e progresso. Credenciais por env, heartbeat sempre ligado, objeto global `hobots`.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
"dist",
|
|
13
13
|
"docs"
|
|
14
14
|
],
|
|
15
|
+
"bin": {
|
|
16
|
+
"hobots": "./dist/cli.cjs"
|
|
17
|
+
},
|
|
15
18
|
"main": "./dist/index.cjs",
|
|
16
19
|
"module": "./dist/index.js",
|
|
17
20
|
"types": "./dist/index.d.ts",
|