hobots 1.8.1 → 1.9.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 +33 -0
- package/dist/index.cjs +277 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -3
- package/dist/index.d.ts +44 -3
- package/dist/index.js +277 -7
- package/dist/index.js.map +1 -1
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.cts +41 -1
- package/dist/protocol.d.ts +41 -1
- package/docs/api-reference.md +27 -1
- package/docs/concepts.md +17 -0
- package/docs/getting-started.md +1 -0
- package/docs/protocol.md +60 -3
- package/docs/tasks.md +69 -0
- package/package.json +1 -1
package/dist/protocol.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/protocol.ts"],"sourcesContent":["/**\n * Tipos do protocolo do Hobots (SDK → servidor).\n *\n * Este arquivo é a fonte de verdade do wire format e só pode reexportar tipos:\n * o app valida os payloads com zod e trava o drift via `satisfies z.ZodType<...>`\n * contra estes tipos (importados de `hobots/protocol`).\n */\n\n// Wire format de tasks (agente ↔ servidor) — reexportado aqui para o app\n// consumir tudo de `hobots/protocol`.\nexport type {\n RunStatus,\n RunUpdateStatus,\n RunLogLevel,\n RunLogEntry,\n AgentInfo,\n PollRequest,\n RunDescriptor,\n PollResponse,\n RunError,\n RunUpdateRequest,\n RunUpdateResponse,\n} from './tasks/protocol';\n\nexport type Severity = 'fatal' | 'error' | 'warning' | 'info' | 'debug' | 'success';\n\n/** Frames em ordem innermost-first (mesma ordem impressa pelo V8). */\nexport interface StackFrame {\n function?: string;\n filename?: string;\n lineno?: number;\n colno?: number;\n in_app: boolean;\n}\n\nexport interface Breadcrumb {\n /** ISO 8601 */\n timestamp: string;\n type?: string;\n category?: string;\n message?: string;\n level?: Severity;\n data?: Record<string, unknown>;\n}\n\nexport interface ExceptionData {\n type: string;\n value: string;\n stacktrace?: { frames: StackFrame[] };\n}\n\nexport interface EventUser {\n id?: string;\n username?: string;\n email?: string;\n}\n\nexport interface EventPayload {\n /** uuid v4 sem hífens (32 hex), gerado pelo SDK */\n event_id: string;\n /** ISO 8601 */\n timestamp: string;\n level: Severity;\n message?: string;\n exception?: ExceptionData;\n /** Override de agrupamento; quando ausente o servidor calcula pelo stack trace */\n fingerprint?: string[];\n breadcrumbs?: Breadcrumb[];\n tags?: Record<string, string>;\n user?: EventUser;\n contexts?: Record<string, Record<string, unknown>>;\n extra?: Record<string, unknown>;\n release?: string;\n environment?: string;\n}\n\n/** Status de uma step (etapa). `warning` = terminou, mas merece atenção (não é falha). */\nexport type StepStatus = 'ok' | 'warning' | 'error' | 'cancelled';\n\nexport interface StepData {\n /** 16 hex */\n step_id: string;\n parent_step_id?: string;\n op?: string;\n description?: string;\n status?: StepStatus;\n /** ISO 8601 */\n start_timestamp: string;\n /** ISO 8601 (fim da step) */\n timestamp: string;\n data?: Record<string, unknown>;\n}\n\nexport interface TransactionPayload {\n event_id: string;\n name: string;\n op?: string;\n status: string;\n start_timestamp: string;\n timestamp: string;\n steps: StepData[];\n tags?: Record<string, string>;\n release?: string;\n environment?: string;\n /** Correlaciona esta transaction a uma execução de task (ctx.tx dentro de um task handler) */\n run_id?: string;\n}\n\nexport interface HeartbeatPayload {\n instance_id: string;\n name?: string;\n interval_ms: number;\n /** ISO 8601 */\n timestamp: string;\n metadata?: Record<string, unknown>;\n release?: string;\n}\n\nexport type EnvelopeItem =\n | { type: 'event'; payload: EventPayload }\n | { type: 'transaction'; payload: TransactionPayload }\n | { type: 'heartbeat'; payload: HeartbeatPayload };\n\nexport interface Envelope {\n sdk: { name: string; version: string };\n /** ISO 8601 */\n sent_at: string;\n items: EnvelopeItem[];\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/protocol.ts"],"sourcesContent":["/**\n * Tipos do protocolo do Hobots (SDK → servidor).\n *\n * Este arquivo é a fonte de verdade do wire format e só pode reexportar tipos:\n * o app valida os payloads com zod e trava o drift via `satisfies z.ZodType<...>`\n * contra estes tipos (importados de `hobots/protocol`).\n */\n\n// Wire format de tasks (agente ↔ servidor) — reexportado aqui para o app\n// consumir tudo de `hobots/protocol`.\nexport type {\n RunStatus,\n RunUpdateStatus,\n RunLogLevel,\n RunLogEntry,\n AgentInfo,\n PollRequest,\n RunDescriptor,\n PollResponse,\n RunError,\n RunUpdateRequest,\n RunUpdateResponse,\n RunAttachmentContentType,\n RunAttachmentCreateRequest,\n RunAttachmentCreateResponse,\n RunAttachmentConfirmRequest,\n RunAttachmentConfirmResponse,\n} from './tasks/protocol';\n\nexport type Severity = 'fatal' | 'error' | 'warning' | 'info' | 'debug' | 'success';\n\n/** Frames em ordem innermost-first (mesma ordem impressa pelo V8). */\nexport interface StackFrame {\n function?: string;\n filename?: string;\n lineno?: number;\n colno?: number;\n in_app: boolean;\n}\n\nexport interface Breadcrumb {\n /** ISO 8601 */\n timestamp: string;\n type?: string;\n category?: string;\n message?: string;\n level?: Severity;\n data?: Record<string, unknown>;\n}\n\nexport interface ExceptionData {\n type: string;\n value: string;\n stacktrace?: { frames: StackFrame[] };\n}\n\nexport interface EventUser {\n id?: string;\n username?: string;\n email?: string;\n}\n\nexport interface EventPayload {\n /** uuid v4 sem hífens (32 hex), gerado pelo SDK */\n event_id: string;\n /** ISO 8601 */\n timestamp: string;\n level: Severity;\n message?: string;\n exception?: ExceptionData;\n /** Override de agrupamento; quando ausente o servidor calcula pelo stack trace */\n fingerprint?: string[];\n breadcrumbs?: Breadcrumb[];\n tags?: Record<string, string>;\n user?: EventUser;\n contexts?: Record<string, Record<string, unknown>>;\n extra?: Record<string, unknown>;\n release?: string;\n environment?: string;\n}\n\n/** Status de uma step (etapa). `warning` = terminou, mas merece atenção (não é falha). */\nexport type StepStatus = 'ok' | 'warning' | 'error' | 'cancelled';\n\nexport interface StepData {\n /** 16 hex */\n step_id: string;\n parent_step_id?: string;\n op?: string;\n description?: string;\n status?: StepStatus;\n /** ISO 8601 */\n start_timestamp: string;\n /** ISO 8601 (fim da step) */\n timestamp: string;\n data?: Record<string, unknown>;\n}\n\nexport interface TransactionPayload {\n event_id: string;\n name: string;\n op?: string;\n status: string;\n start_timestamp: string;\n timestamp: string;\n steps: StepData[];\n tags?: Record<string, string>;\n release?: string;\n environment?: string;\n /** Correlaciona esta transaction a uma execução de task (ctx.tx dentro de um task handler) */\n run_id?: string;\n}\n\nexport interface HeartbeatPayload {\n instance_id: string;\n name?: string;\n interval_ms: number;\n /** ISO 8601 */\n timestamp: string;\n metadata?: Record<string, unknown>;\n release?: string;\n}\n\nexport type EnvelopeItem =\n | { type: 'event'; payload: EventPayload }\n | { type: 'transaction'; payload: TransactionPayload }\n | { type: 'heartbeat'; payload: HeartbeatPayload };\n\nexport interface Envelope {\n sdk: { name: string; version: string };\n /** ISO 8601 */\n sent_at: string;\n items: EnvelopeItem[];\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
package/dist/protocol.d.cts
CHANGED
|
@@ -15,6 +15,12 @@ interface RunLogEntry {
|
|
|
15
15
|
ts: string;
|
|
16
16
|
level: RunLogLevel;
|
|
17
17
|
message: string;
|
|
18
|
+
/**
|
|
19
|
+
* Ids dos anexos (imagens) desta linha — gerados pelo agente (32 hex) e criados via
|
|
20
|
+
* `POST /v1/tasks/runs/{id}/attachments`. O upload roda em segundo plano: a linha pode
|
|
21
|
+
* chegar ao servidor antes do binário estar no S3.
|
|
22
|
+
*/
|
|
23
|
+
attachments?: string[];
|
|
18
24
|
}
|
|
19
25
|
interface AgentInfo {
|
|
20
26
|
/** Identidade canônica do agente — correlaciona com o heartbeat de telemetria */
|
|
@@ -68,6 +74,40 @@ interface RunUpdateRequest {
|
|
|
68
74
|
interface RunUpdateResponse {
|
|
69
75
|
ok: true;
|
|
70
76
|
}
|
|
77
|
+
/** Formatos aceitos para anexo. O S3 valida o content-type contra a assinatura. */
|
|
78
|
+
type RunAttachmentContentType = 'image/png' | 'image/jpeg' | 'image/webp';
|
|
79
|
+
/** POST /v1/tasks/runs/{id}/attachments */
|
|
80
|
+
interface RunAttachmentCreateRequest {
|
|
81
|
+
/** Id do anexo gerado pelo agente (32 hex) — já referenciado na linha de log */
|
|
82
|
+
id: string;
|
|
83
|
+
content_type: RunAttachmentContentType;
|
|
84
|
+
/** Tamanho exato do binário; o PUT presignado é assinado com este Content-Length */
|
|
85
|
+
size_bytes: number;
|
|
86
|
+
caption?: string;
|
|
87
|
+
/** ISO 8601 — ts da linha de log que referencia o anexo */
|
|
88
|
+
log_ts?: string;
|
|
89
|
+
width?: number;
|
|
90
|
+
height?: number;
|
|
91
|
+
}
|
|
92
|
+
interface RunAttachmentCreateResponse {
|
|
93
|
+
id: string;
|
|
94
|
+
/** URL presignada de PUT direto no S3 (não envie Authorization nesta requisição) */
|
|
95
|
+
upload_url: string;
|
|
96
|
+
/** Headers obrigatórios no PUT — fazem parte da assinatura */
|
|
97
|
+
headers: Record<string, string>;
|
|
98
|
+
/** Validade da URL, em segundos */
|
|
99
|
+
expires_in: number;
|
|
100
|
+
}
|
|
101
|
+
/** PATCH /v1/tasks/runs/{id}/attachments/{attachmentId} */
|
|
102
|
+
interface RunAttachmentConfirmRequest {
|
|
103
|
+
status: 'uploaded' | 'failed';
|
|
104
|
+
size_bytes?: number;
|
|
105
|
+
width?: number;
|
|
106
|
+
height?: number;
|
|
107
|
+
}
|
|
108
|
+
interface RunAttachmentConfirmResponse {
|
|
109
|
+
ok: true;
|
|
110
|
+
}
|
|
71
111
|
|
|
72
112
|
/**
|
|
73
113
|
* Tipos do protocolo do Hobots (SDK → servidor).
|
|
@@ -183,4 +223,4 @@ interface Envelope {
|
|
|
183
223
|
items: EnvelopeItem[];
|
|
184
224
|
}
|
|
185
225
|
|
|
186
|
-
export type { AgentInfo, Breadcrumb, Envelope, EnvelopeItem, EventPayload, EventUser, ExceptionData, HeartbeatPayload, PollRequest, PollResponse, RunDescriptor, RunError, RunLogEntry, RunLogLevel, RunStatus, RunUpdateRequest, RunUpdateResponse, RunUpdateStatus, Severity, StackFrame, StepData, StepStatus, TransactionPayload };
|
|
226
|
+
export type { AgentInfo, Breadcrumb, Envelope, EnvelopeItem, EventPayload, EventUser, ExceptionData, HeartbeatPayload, PollRequest, PollResponse, RunAttachmentConfirmRequest, RunAttachmentConfirmResponse, RunAttachmentContentType, RunAttachmentCreateRequest, RunAttachmentCreateResponse, RunDescriptor, RunError, RunLogEntry, RunLogLevel, RunStatus, RunUpdateRequest, RunUpdateResponse, RunUpdateStatus, Severity, StackFrame, StepData, StepStatus, TransactionPayload };
|
package/dist/protocol.d.ts
CHANGED
|
@@ -15,6 +15,12 @@ interface RunLogEntry {
|
|
|
15
15
|
ts: string;
|
|
16
16
|
level: RunLogLevel;
|
|
17
17
|
message: string;
|
|
18
|
+
/**
|
|
19
|
+
* Ids dos anexos (imagens) desta linha — gerados pelo agente (32 hex) e criados via
|
|
20
|
+
* `POST /v1/tasks/runs/{id}/attachments`. O upload roda em segundo plano: a linha pode
|
|
21
|
+
* chegar ao servidor antes do binário estar no S3.
|
|
22
|
+
*/
|
|
23
|
+
attachments?: string[];
|
|
18
24
|
}
|
|
19
25
|
interface AgentInfo {
|
|
20
26
|
/** Identidade canônica do agente — correlaciona com o heartbeat de telemetria */
|
|
@@ -68,6 +74,40 @@ interface RunUpdateRequest {
|
|
|
68
74
|
interface RunUpdateResponse {
|
|
69
75
|
ok: true;
|
|
70
76
|
}
|
|
77
|
+
/** Formatos aceitos para anexo. O S3 valida o content-type contra a assinatura. */
|
|
78
|
+
type RunAttachmentContentType = 'image/png' | 'image/jpeg' | 'image/webp';
|
|
79
|
+
/** POST /v1/tasks/runs/{id}/attachments */
|
|
80
|
+
interface RunAttachmentCreateRequest {
|
|
81
|
+
/** Id do anexo gerado pelo agente (32 hex) — já referenciado na linha de log */
|
|
82
|
+
id: string;
|
|
83
|
+
content_type: RunAttachmentContentType;
|
|
84
|
+
/** Tamanho exato do binário; o PUT presignado é assinado com este Content-Length */
|
|
85
|
+
size_bytes: number;
|
|
86
|
+
caption?: string;
|
|
87
|
+
/** ISO 8601 — ts da linha de log que referencia o anexo */
|
|
88
|
+
log_ts?: string;
|
|
89
|
+
width?: number;
|
|
90
|
+
height?: number;
|
|
91
|
+
}
|
|
92
|
+
interface RunAttachmentCreateResponse {
|
|
93
|
+
id: string;
|
|
94
|
+
/** URL presignada de PUT direto no S3 (não envie Authorization nesta requisição) */
|
|
95
|
+
upload_url: string;
|
|
96
|
+
/** Headers obrigatórios no PUT — fazem parte da assinatura */
|
|
97
|
+
headers: Record<string, string>;
|
|
98
|
+
/** Validade da URL, em segundos */
|
|
99
|
+
expires_in: number;
|
|
100
|
+
}
|
|
101
|
+
/** PATCH /v1/tasks/runs/{id}/attachments/{attachmentId} */
|
|
102
|
+
interface RunAttachmentConfirmRequest {
|
|
103
|
+
status: 'uploaded' | 'failed';
|
|
104
|
+
size_bytes?: number;
|
|
105
|
+
width?: number;
|
|
106
|
+
height?: number;
|
|
107
|
+
}
|
|
108
|
+
interface RunAttachmentConfirmResponse {
|
|
109
|
+
ok: true;
|
|
110
|
+
}
|
|
71
111
|
|
|
72
112
|
/**
|
|
73
113
|
* Tipos do protocolo do Hobots (SDK → servidor).
|
|
@@ -183,4 +223,4 @@ interface Envelope {
|
|
|
183
223
|
items: EnvelopeItem[];
|
|
184
224
|
}
|
|
185
225
|
|
|
186
|
-
export type { AgentInfo, Breadcrumb, Envelope, EnvelopeItem, EventPayload, EventUser, ExceptionData, HeartbeatPayload, PollRequest, PollResponse, RunDescriptor, RunError, RunLogEntry, RunLogLevel, RunStatus, RunUpdateRequest, RunUpdateResponse, RunUpdateStatus, Severity, StackFrame, StepData, StepStatus, TransactionPayload };
|
|
226
|
+
export type { AgentInfo, Breadcrumb, Envelope, EnvelopeItem, EventPayload, EventUser, ExceptionData, HeartbeatPayload, PollRequest, PollResponse, RunAttachmentConfirmRequest, RunAttachmentConfirmResponse, RunAttachmentContentType, RunAttachmentCreateRequest, RunAttachmentCreateResponse, RunDescriptor, RunError, RunLogEntry, RunLogLevel, RunStatus, RunUpdateRequest, RunUpdateResponse, RunUpdateStatus, Severity, StackFrame, StepData, StepStatus, TransactionPayload };
|
package/docs/api-reference.md
CHANGED
|
@@ -159,6 +159,7 @@ interface RunContext {
|
|
|
159
159
|
signal: AbortSignal;
|
|
160
160
|
throwIfCanceled(): void; // lança RunCanceled se o signal já disparou
|
|
161
161
|
sleep(ms: number): Promise<void>; // espera cancelável — rejeita com RunCanceled no abort
|
|
162
|
+
attach(image: Uint8Array | ArrayBuffer | string, options?: AttachOptions): Promise<AttachResult>;
|
|
162
163
|
}
|
|
163
164
|
```
|
|
164
165
|
|
|
@@ -168,6 +169,26 @@ interface RunContext {
|
|
|
168
169
|
- **`signal`** — `AbortSignal` que dispara em cancelamento no app, timeout ou shutdown.
|
|
169
170
|
- **`throwIfCanceled()`** — o ponto de parada de uma linha: lança `RunCanceled` se o `signal` já disparou.
|
|
170
171
|
- **`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.
|
|
172
|
+
- **`attach(image, options?)`** — anexa um print à linha de log. Aceita bytes (`Uint8Array`/`ArrayBuffer`/`Buffer`) ou um caminho de arquivo (só em Node). A linha de log entra na hora; o upload é em segundo plano e **nunca lança**. Guia: [Anexos](./tasks.md#anexos-screenshots).
|
|
173
|
+
|
|
174
|
+
### AttachOptions
|
|
175
|
+
|
|
176
|
+
| Campo | Tipo | Default | O que faz |
|
|
177
|
+
| --- | --- | --- | --- |
|
|
178
|
+
| `caption` | `string` | — | legenda exibida sob a imagem ampliada; truncada em 200 caracteres e passada pelo scrub de segredos |
|
|
179
|
+
| `level` | `RunLogLevel` | `'info'` | nível da linha de log que carrega o anexo |
|
|
180
|
+
| `message` | `string` | `caption` ou `'captura de tela'` | texto da linha de log |
|
|
181
|
+
| `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 |
|
|
182
|
+
|
|
183
|
+
### AttachResult
|
|
184
|
+
|
|
185
|
+
| Campo | Tipo | O que é |
|
|
186
|
+
| --- | --- | --- |
|
|
187
|
+
| `id` | `string` | id do anexo — o mesmo referenciado na linha de log |
|
|
188
|
+
| `ok` | `boolean` | `true` quando o binário chegou ao S3 |
|
|
189
|
+
| `reason` | `AttachFailureReason?` | `'too-large' \| 'quota' \| 'unsupported-format' \| 'read-failed' \| 'unsupported' \| 'upload-failed' \| 'canceled'` |
|
|
190
|
+
|
|
191
|
+
Limites (todos aplicados também no servidor): PNG/JPEG/WEBP, **5 MB** por imagem, **20** anexos por execução, **4** por linha de log.
|
|
171
192
|
|
|
172
193
|
---
|
|
173
194
|
|
|
@@ -180,7 +201,8 @@ Todos os prazos do SDK num lugar só. A coluna **Onde configura** diz se o valor
|
|
|
180
201
|
| Tempo | Default | Onde configura | O que acontece |
|
|
181
202
|
| --- | --- | --- | --- |
|
|
182
203
|
| flush automático | `2000` ms | `init({ flushIntervalMs })` | de quanto em quanto o transport esvazia a fila; menor = mais requests, telemetria mais fresca |
|
|
183
|
-
|
|
|
204
|
+
| 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 |
|
|
205
|
+
| 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 |
|
|
184
206
|
| teto da fila | `200` itens | — (fixo) | fila cheia descarta o item **mais antigo**, com aviso no console |
|
|
185
207
|
| `flush(timeoutMs)` | `5000` ms | argumento | quanto espera os envios em voo antes de devolver `false` |
|
|
186
208
|
| `close(timeoutMs)` | `5000` ms | argumento | idem, no flush final (veja a nota 1 abaixo) |
|
|
@@ -193,6 +215,10 @@ Todos os prazos do SDK num lugar só. A coluna **Onde configura** diz se o valor
|
|
|
193
215
|
| --- | --- | --- | --- |
|
|
194
216
|
| 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)) |
|
|
195
217
|
| flush de logs da run | `2000` ms | `tasks: { logFlushIntervalMs }` | ou na hora, ao acumular 20 linhas |
|
|
218
|
+
| validade da URL de upload de anexo | `900` s | — (servidor) | tempo para o PUT no S3 depois que a API presigna |
|
|
219
|
+
| 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" |
|
|
220
|
+
| uploads de anexo simultâneos | `3` | — (fixo) | por execução; os demais ficam na fila |
|
|
221
|
+
| retry do upload de anexo | `1s, 4s` | — (fixo) | ladder curta: o binário é grande e a URL presignada tem prazo |
|
|
196
222
|
| 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) |
|
|
197
223
|
| 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 |
|
|
198
224
|
| `stop(timeoutMs)` | `30000` ms | argumento | espera as runs ativas; no estouro elas são reportadas como `AgentTerminated` |
|
package/docs/concepts.md
CHANGED
|
@@ -365,6 +365,23 @@ O agente só transiciona para `running`, `succeeded` e `failed`. `pending` e `cl
|
|
|
365
365
|
|
|
366
366
|
Cada run carrega os **`params`** (o JSON validado do formulário) e chega ao seu código como o primeiro argumento do handler, junto de um [`RunContext`](./api-reference.md#taskhandler--runcontext) com logs, sinal de cancelamento e a [transaction](#transaction-de-run-ctxtx).
|
|
367
367
|
|
|
368
|
+
## Anexo
|
|
369
|
+
|
|
370
|
+
Um **anexo** é uma imagem — na prática, um print da tela — vinculada a uma linha de log de uma [run](#run). Serve para responder "como estava a tela quando isso aconteceu?", pergunta que texto de log sozinho não responde. No app, aparece como miniatura logo abaixo da linha de log correspondente, na aba "logs do agente" da execução.
|
|
371
|
+
|
|
372
|
+
Quem captura é o seu código; o SDK só recebe a imagem pronta via [`ctx.attach()`](./tasks.md#anexos-screenshots).
|
|
373
|
+
|
|
374
|
+
**O binário não passa pela API do Hobots.** O agente pede uma URL presignada, sobe direto no S3 e depois confirma. Isso mantém a API livre de tráfego pesado e permite imagens bem maiores do que o limite de corpo de um PATCH. O ciclo de vida de um anexo é:
|
|
375
|
+
|
|
376
|
+
```
|
|
377
|
+
pending → uploaded
|
|
378
|
+
↘ failed
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
O `pending` existe porque a linha de log é registrada **antes** do upload terminar — é isso que impede o print de atrasar o log ou bagunçar a ordem. Enquanto o anexo está `pending`, o app mostra a miniatura como "enviando imagem…".
|
|
382
|
+
|
|
383
|
+
Anexos herdam o mesmo gate de visibilidade dos logs: solicitantes (papel `requester`) só os veem quando o projeto está com "mostrar logs para solicitantes" ligado.
|
|
384
|
+
|
|
368
385
|
## Claim
|
|
369
386
|
|
|
370
387
|
O **claim** é o ato de um agente reivindicar uma run para si. Importa porque resolve a pergunta óbvia de qualquer sistema de fila: **o que impede dois agentes de pegarem a mesma run?**
|
package/docs/getting-started.md
CHANGED
|
@@ -88,6 +88,7 @@ Hobots.init({
|
|
|
88
88
|
|
|
89
89
|
Hobots.register('processar-nfe', async (params, ctx) => {
|
|
90
90
|
ctx.log.info(`processando lote ${params.lote}…`);
|
|
91
|
+
// await ctx.attach(await page.screenshot(), { caption: 'portal após o login' }); // print junto do log
|
|
91
92
|
ctx.log.info('12 notas processadas'); // o retorno do handler é ignorado — reporte por log
|
|
92
93
|
});
|
|
93
94
|
Hobots.start(); // mantém o processo vivo até stop()/close()
|
package/docs/protocol.md
CHANGED
|
@@ -16,6 +16,7 @@ hb_<64 hex>
|
|
|
16
16
|
| --- | --- | --- |
|
|
17
17
|
| Telemetria (ingestão) | `POST <baseUrl>/v1/ingest` | `Authorization: Bearer <clientSecret>` |
|
|
18
18
|
| Tasks (execução) | `<baseUrl>/v1/tasks/*` | `Authorization: Bearer <clientSecret>` |
|
|
19
|
+
| Anexos (binário) | URL presignada do S3 (PUT) | assinatura na própria URL — **sem** header `Authorization` |
|
|
19
20
|
|
|
20
21
|
A resolução da credencial nos endpoints é interna ao `init()` (em `src/config.ts`, com a constante exportada `DEFAULT_BASE_URL = 'https://api2.hobots.app'`); o resultado fica acessível em `getClient()?.config` → `{ key, baseUrl, ingestEndpoint, tasksBaseUrl }`. Em desenvolvimento a env `HOBOTS_API_URL` sobrescreve a URL base — apenas `http(s)`, e `http` para host fora de loopback gera aviso porque o secret trafega sem TLS. A credencial é **secreta** (formato `hb_` + 64 hex — chaves de outro formato são rejeitadas no `init()`) e viaja **só no header** `Authorization: Bearer` — nunca na URL da requisição.
|
|
21
22
|
|
|
@@ -39,6 +40,8 @@ Tipos de item: `event`, `transaction`, `heartbeat` (definidos em `hobots/protoco
|
|
|
39
40
|
|
|
40
41
|
Um `transaction` pode carregar `run_id`: é assim que a [transaction de uma run](./concepts.md#transaction-de-run-ctxtx) (`ctx.tx`) é correlacionada à execução, e o que permite o app montar o waterfall de performance dentro da solicitação. Transactions abertas com `startTransaction()` fora de uma run não têm esse campo.
|
|
41
42
|
|
|
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
|
+
|
|
42
45
|
**Flush & retries:** o transport flusha por `flushIntervalMs` ou ao atingir `maxBatchSize`. Ladder de retry de 3 tentativas; respostas 4xx (exceto 429) são descartadas sem retry; a fila tem teto fixo e descarta os itens **mais antigos** ao encher. Todos os valores em [Tempos e intervalos](./api-reference.md#tempos-e-intervalos).
|
|
43
46
|
|
|
44
47
|
## Tasks — polling e reporte
|
|
@@ -76,12 +79,17 @@ Corpo `RunUpdateRequest`:
|
|
|
76
79
|
```jsonc
|
|
77
80
|
{
|
|
78
81
|
"status": "running", // 'running' | 'succeeded' | 'failed'
|
|
79
|
-
"logs": [
|
|
80
|
-
|
|
82
|
+
"logs": [
|
|
83
|
+
{ "ts": "…", "level": "info", "message": "…" }, // delta; o servidor concatena
|
|
84
|
+
{ "ts": "…", "level": "info", "message": "portal após o login",
|
|
85
|
+
"attachments": ["<32 hex>"] } // opcional; ids de anexo (máx. 4)
|
|
86
|
+
],
|
|
87
|
+
"error": { "type": "Error", "message": "…", "stack": "…" } // só com 'failed'
|
|
81
88
|
}
|
|
82
89
|
```
|
|
83
90
|
|
|
84
|
-
- `status: 'running'` pode repetir para enviar **logs incrementais** (throttle por `logFlushIntervalMs`, flush ao acumular 20, chunk de 100, teto de
|
|
91
|
+
- `status: 'running'` pode repetir para enviar **logs incrementais** (throttle por `logFlushIntervalMs`, flush ao acumular 20, chunk de 100, teto de 1000 logs/run, mensagem cap 10k).
|
|
92
|
+
- `attachments` é **opcional** e aditivo: SDKs anteriores à 1.9 simplesmente não enviam o campo, e uma API anterior ao recurso descarta o campo na validação (objeto não-strict) sem rejeitar o PATCH. Os ids são gerados pelo agente **antes** do upload, o que permite enviar a linha de log sem esperar o binário subir.
|
|
85
93
|
- Ordem garantida `running → logs → terminal` (PATCHes serializados numa cadeia de promise).
|
|
86
94
|
- Retry `[1s, 4s, 16s]`; 4xx exceto 429 é terminal (**409** = run cancelada / transição inválida → marca a run como morta e dispara `ctx.signal`).
|
|
87
95
|
- Todo PATCH atualiza `last_report_at` no servidor (relógio do servidor) — é o **sinal de vida da run**. O heartbeat de cancelamento (PATCH `running` vazio a cada `cancelCheckIntervalMs`, máx 15s) garante reporte contínuo; um sweep server-side marca como `failed` com erro `AgentTerminated` ("execução encerrada inesperadamente") toda run `running` sem reporte há **60s**, sem re-entrega. No exit path (sinal/erro fatal com runs ativas), o SDK envia um único PATCH `failed`/`AgentTerminated` com timeout curto (~2s), sem retry — o sweep é o backstop.
|
|
@@ -89,6 +97,55 @@ Corpo `RunUpdateRequest`:
|
|
|
89
97
|
|
|
90
98
|
Ciclo de vida completo da run no servidor (`RunStatus`): `pending → claimed → running → succeeded | failed | canceled`. O agente só transiciona para `running`/`succeeded`/`failed` (`RunUpdateStatus`).
|
|
91
99
|
|
|
100
|
+
### `POST /v1/tasks/runs/{id}/attachments`
|
|
101
|
+
|
|
102
|
+
Registra um anexo de imagem e devolve uma **URL presignada de PUT**. O binário nunca passa pela API: o agente sobe direto no S3.
|
|
103
|
+
|
|
104
|
+
Corpo `RunAttachmentCreateRequest`:
|
|
105
|
+
|
|
106
|
+
```jsonc
|
|
107
|
+
{
|
|
108
|
+
"id": "<32 hex>", // gerado pelo agente, já referenciado na linha de log
|
|
109
|
+
"content_type": "image/png", // 'image/png' | 'image/jpeg' | 'image/webp'
|
|
110
|
+
"size_bytes": 184320, // exato — entra na assinatura da URL
|
|
111
|
+
"caption": "portal após o login", // opcional, máx. 500
|
|
112
|
+
"log_ts": "…", // opcional, ISO 8601 — ts da linha de log
|
|
113
|
+
"width": 1920, "height": 1080 // opcionais
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Resposta `200` `RunAttachmentCreateResponse`:
|
|
118
|
+
|
|
119
|
+
```jsonc
|
|
120
|
+
{
|
|
121
|
+
"id": "<32 hex>",
|
|
122
|
+
"upload_url": "https://…", // PUT direto no S3, SEM Authorization
|
|
123
|
+
"headers": { "content-type": "image/png", "content-length": "184320" },
|
|
124
|
+
"expires_in": 900
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
O PUT no S3 precisa mandar exatamente esses headers — eles fazem parte da assinatura. `content-length` é assinado de propósito: sem isso a URL aceitaria um upload de qualquer tamanho.
|
|
129
|
+
|
|
130
|
+
| Status | Quando |
|
|
131
|
+
| --- | --- |
|
|
132
|
+
| `401` | chave do agente inválida ou inativa |
|
|
133
|
+
| `404` | execução inexistente ou de outro projeto |
|
|
134
|
+
| `409` | id de anexo já registrado |
|
|
135
|
+
| `413` | corpo acima de 8 KB (é só metadado) |
|
|
136
|
+
| `422` | payload inválido |
|
|
137
|
+
| `429` | limite de 20 anexos por execução atingido |
|
|
138
|
+
| `503` | anexos não habilitados nesta instalação (sem bucket S3 configurado) |
|
|
139
|
+
| `502` | falha ao presignar — o registro é desfeito |
|
|
140
|
+
|
|
141
|
+
### `PATCH /v1/tasks/runs/{id}/attachments/{attachmentId}`
|
|
142
|
+
|
|
143
|
+
Confirma o upload. Corpo `RunAttachmentConfirmRequest`: `{ "status": "uploaded" | "failed", "size_bytes"?, "width"?, "height"? }`; resposta `{ "ok": true }`, ou `404` se o anexo não existe no projeto da chave.
|
|
144
|
+
|
|
145
|
+
É **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.
|
|
146
|
+
|
|
147
|
+
Limites de anexo (aplicados no SDK **e** no servidor): PNG/JPEG/WEBP, 5 MB por imagem, 20 por execução, 4 por linha de log.
|
|
148
|
+
|
|
92
149
|
## Onde vive o claim atômico
|
|
93
150
|
|
|
94
151
|
O `POST /v1/tasks/poll` no app chama `claimPendingRuns()` em
|
package/docs/tasks.md
CHANGED
|
@@ -52,6 +52,7 @@ interface RunContext {
|
|
|
52
52
|
signal: AbortSignal;
|
|
53
53
|
throwIfCanceled(): void;
|
|
54
54
|
sleep(ms: number): Promise<void>;
|
|
55
|
+
attach(image, options?): Promise<AttachResult>; // anexa um print a uma linha de log
|
|
55
56
|
}
|
|
56
57
|
```
|
|
57
58
|
|
|
@@ -61,6 +62,7 @@ interface RunContext {
|
|
|
61
62
|
- **`ctx.signal`** — `AbortSignal` que dispara quando a run é **cancelada no app**, em **timeout** ou no **shutdown**. Cancelamento é cooperativo (veja abaixo).
|
|
62
63
|
- **`ctx.throwIfCanceled()`** — lança `RunCanceled` se o `signal` já disparou. O ponto de parada de 1 linha para chamar entre as etapas do trabalho.
|
|
63
64
|
- **`ctx.sleep(ms)`** — sleep que respeita o `signal`: rejeita com `RunCanceled` no cancelamento em vez de esperar até o fim. Use no lugar do seu `sleep` em esperas longas.
|
|
65
|
+
- **`ctx.attach(image, options?)`** — anexa um print da tela a uma linha de log (veja [Anexos](#anexos-screenshots) abaixo).
|
|
64
66
|
- **Retorno** — **ignorado**. O SDK não captura nem envia o valor retornado pelo handler ao servidor — resultados de execução podem conter dados sensíveis e não são coletados.
|
|
65
67
|
- **Erro lançado** — a run é reportada como `failed` com tipo/mensagem/stack (mensagem cap 10k, stack 20k).
|
|
66
68
|
|
|
@@ -68,6 +70,73 @@ Você pode tipar os params: `Hobots.register<{ lote: number }>(...)`.
|
|
|
68
70
|
|
|
69
71
|
> **Dados sensíveis:** o que você escreve em `ctx.log` e as mensagens/stack de erros lançados **são enviados ao servidor** e ficam visíveis na execução no app. Não inclua senhas, tokens, documentos, dados pessoais ou qualquer informação sensível em logs e mensagens de erro.
|
|
70
72
|
|
|
73
|
+
## Anexos (screenshots)
|
|
74
|
+
|
|
75
|
+
Texto de log conta o que o robô fez; um print conta **como estava a tela**. `ctx.attach()` anexa uma imagem a uma linha de log, e ela aparece como miniatura logo abaixo dela na aba "logs do agente" da execução — clicável para ampliar.
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
Hobots.register('processar-nfe', async (params, ctx) => {
|
|
79
|
+
await entrar();
|
|
80
|
+
await ctx.attach(await page.screenshot(), { caption: 'portal após o login' });
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
await emitir(params);
|
|
84
|
+
} catch (erro) {
|
|
85
|
+
// numa falha é justamente quando o print importa
|
|
86
|
+
await ctx.attach(await page.screenshot(), { caption: 'tela no momento do erro', level: 'error' });
|
|
87
|
+
throw erro;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**O SDK não tira o print.** Ele recebe a imagem pronta — de onde você quiser: `page.screenshot()` do Playwright/Puppeteer, uma biblioteca de captura de desktop, um arquivo que o próprio sistema gerou. Isso mantém o SDK sem dependências nativas e funcionando em qualquer ambiente.
|
|
93
|
+
|
|
94
|
+
### O que aceita
|
|
95
|
+
|
|
96
|
+
| Entrada | Exemplo |
|
|
97
|
+
| --- | --- |
|
|
98
|
+
| bytes | `Uint8Array`, `ArrayBuffer` ou `Buffer` — `await page.screenshot()` |
|
|
99
|
+
| caminho de arquivo | `'C:\\prints\\tela.png'` (só em Node) |
|
|
100
|
+
|
|
101
|
+
### Opções
|
|
102
|
+
|
|
103
|
+
| Opção | Tipo | Default | O que faz |
|
|
104
|
+
| --- | --- | --- | --- |
|
|
105
|
+
| `caption` | `string` | — | legenda curta exibida sob a imagem ampliada (máx. 200 caracteres) |
|
|
106
|
+
| `level` | `RunLogLevel` | `'info'` | nível da linha de log que carrega o anexo |
|
|
107
|
+
| `message` | `string` | a legenda, ou `'captura de tela'` | texto da linha de log |
|
|
108
|
+
| `contentType` | `'image/png' \| 'image/jpeg' \| 'image/webp'` | detectado | só é usado quando os *magic bytes* não bastam |
|
|
109
|
+
|
|
110
|
+
### Limites
|
|
111
|
+
|
|
112
|
+
| Limite | Valor |
|
|
113
|
+
| --- | --- |
|
|
114
|
+
| formatos | PNG, JPEG e WEBP |
|
|
115
|
+
| tamanho por imagem | 5 MB |
|
|
116
|
+
| anexos por execução | 20 |
|
|
117
|
+
| anexos por linha de log | 4 |
|
|
118
|
+
| uploads simultâneos | 3 (o resto fica na fila) |
|
|
119
|
+
|
|
120
|
+
### Como funciona (e por que não trava a run)
|
|
121
|
+
|
|
122
|
+
A linha de log é registrada **na hora**, com o id do anexo; o upload roda em segundo plano. O binário vai direto para o S3 por uma URL presignada — não passa pela API do Hobots. Enquanto o upload não termina, o app mostra a miniatura como "enviando imagem…".
|
|
123
|
+
|
|
124
|
+
`attach()` **nunca lança**. Todo insucesso vira `{ ok: false, reason }` e um aviso no log da run:
|
|
125
|
+
|
|
126
|
+
| `reason` | Quando |
|
|
127
|
+
| --- | --- |
|
|
128
|
+
| `too-large` | acima de 5 MB |
|
|
129
|
+
| `quota` | passou de 20 anexos na execução |
|
|
130
|
+
| `unsupported-format` | não é PNG/JPEG/WEBP |
|
|
131
|
+
| `read-failed` | o caminho de arquivo não pôde ser lido |
|
|
132
|
+
| `unsupported` | a instalação do Hobots não tem anexos habilitados |
|
|
133
|
+
| `upload-failed` | falha de rede depois dos retries |
|
|
134
|
+
| `canceled` | a run foi cancelada ou encerrada durante o upload |
|
|
135
|
+
|
|
136
|
+
Você pode `await` para saber o desfecho, ou disparar sem esperar (`void ctx.attach(...)`) — antes de reportar o desfecho da run, o SDK aguarda os uploads pendentes por até 15 s, para a execução não terminar com miniaturas em "enviando…".
|
|
137
|
+
|
|
138
|
+
> **Dados sensíveis:** o print é enviado ao servidor **exatamente como está** — o SDK não borra nem recorta nada. Uma tela de portal costuma conter CPF, nome, saldo, número de documento. Capture apenas o necessário e evite prints de telas com dados de terceiros. Solicitantes (papel `requester`) só veem os anexos quando o projeto está com "mostrar logs para solicitantes" ligado, o mesmo gate dos logs.
|
|
139
|
+
|
|
71
140
|
## Performance da run (`ctx.tx`)
|
|
72
141
|
|
|
73
142
|
O SDK já abre uma [transaction](./concepts.md#transaction-de-run-ctxtx) para cada run e entrega em `ctx.tx`. Ela é correlacionada à execução, recebe o status conforme o desfecho (`ok`, `error` ou `cancelled`) e é **finalizada automaticamente** quando o handler termina. Você não cria e não finaliza — só marca as etapas:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hobots",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "SDK JavaScript da Hobots — telemetria (erros, transactions e heartbeats) e execução remota de tarefas sob demanda (tasks) para RPAs e integrações. Um único import e um único init().",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|