hobots 2.0.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/protocol.ts"],"sourcesContent":["/**\n * Tipos do protocolo do Hobots (SDK → servidor).\n *\n * Este arquivo é a fonte de verdade do wire format e só pode reexportar tipos:\n * o app valida os payloads com zod e trava o drift via `satisfies z.ZodType<...>`\n * contra estes tipos (importados de `hobots/protocol`).\n */\n\n// Wire format de tasks (agente ↔ servidor) — reexportado aqui para o app\n// consumir tudo de `hobots/protocol`.\nexport type {\n RunStatus,\n RunUpdateStatus,\n RunLogLevel,\n RunLogEntry,\n InstanceInfo,\n PollRequest,\n RunDescriptor,\n PollResponse,\n RunError,\n RunUpdateRequest,\n RunUpdateResponse,\n RunItemStatus,\n RunItemInput,\n RunItemsCreateRequest,\n RunItemsCreateResponse,\n RunAttachmentContentType,\n RunAttachmentCreateRequest,\n RunAttachmentCreateResponse,\n RunAttachmentConfirmRequest,\n RunAttachmentConfirmResponse,\n} from './tasks/protocol';\n\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?: 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\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';\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?: 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 /** 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,3 +1,54 @@
1
+ /**
2
+ * Snapshot dos recursos da máquina onde a instância roda.
3
+ *
4
+ * Arquivo-folha do protocolo (não importa nada): reexportado por `hobots/protocol`
5
+ * e referenciado pelo heartbeat, pelo poll de tasks (`InstanceInfo.machine`) e por
6
+ * `contexts.machine` nos eventos. Bytes em bytes; percentuais 0..100. Campo ausente =
7
+ * não mensurável nesta plataforma/runtime.
8
+ */
9
+ interface MachineProcess {
10
+ pid: number;
11
+ /** segundos desde o `init()` do SDK (Python) / início do processo (Node) */
12
+ uptime_s: number;
13
+ /** memória residente do processo do bot */
14
+ rss_bytes?: number;
15
+ /** CPU do processo, 0..100 normalizado pelo número de cores (delta entre coletas); null até haver 2 amostras */
16
+ cpu_percent?: number | null;
17
+ /** só Node: `process.memoryUsage().heapUsed` */
18
+ heap_used_bytes?: number;
19
+ /** só Python: `threading.active_count()` */
20
+ threads?: number;
21
+ }
22
+ interface MachineSnapshot {
23
+ /** ISO 8601 — momento da coleta (relógio do cliente) */
24
+ sampled_at: string;
25
+ /** `InitOptions.instanceId` — liga o snapshot de um evento à série da instância */
26
+ instance_id?: string;
27
+ hostname?: string;
28
+ /** `process.platform` / `sys.platform`: 'linux' | 'win32' | 'darwin' | ... */
29
+ os?: string;
30
+ arch?: string;
31
+ /** cores lógicos */
32
+ cpu_count?: number;
33
+ /** uso de CPU do sistema, 0..100 — média entre duas coletas; null até haver 2 amostras */
34
+ cpu_percent?: number | null;
35
+ /** load average de 1 min (POSIX; ausente no Windows) */
36
+ load_1?: number;
37
+ mem_total_bytes?: number;
38
+ mem_available_bytes?: number;
39
+ /** (total - disponível) / total × 100 */
40
+ mem_percent?: number;
41
+ /** volume medido — o diretório de trabalho do processo */
42
+ disk_path?: string;
43
+ disk_total_bytes?: number;
44
+ /** disponível para o usuário (bavail) */
45
+ disk_free_bytes?: number;
46
+ disk_percent?: number;
47
+ process: MachineProcess;
48
+ /** de onde vieram os números */
49
+ source?: 'stdlib' | 'psutil' | 'stdlib-loadavg';
50
+ }
51
+
1
52
  /**
2
53
  * Tipos do protocolo de tasks do Hobots (agente SDK ↔ servidor).
3
54
  *
@@ -5,6 +56,7 @@
5
56
  * o app valida os payloads com zod e trava o drift via `satisfies z.ZodType<...>`
6
57
  * contra estes tipos (reexportados por `hobots/protocol`).
7
58
  */
59
+
8
60
  /** Ciclo de vida completo de uma run no servidor. */
9
61
  type RunStatus = 'pending' | 'claimed' | 'running' | 'succeeded' | 'failed' | 'canceled';
10
62
  /** Subconjunto que o agente pode enviar no PATCH. */
@@ -37,6 +89,8 @@ interface InstanceInfo {
37
89
  * agentes poll-only, que sem isso herdariam a tolerância do heartbeat (30s × 3).
38
90
  */
39
91
  poll_interval_ms?: number;
92
+ /** Recursos da máquina no momento do poll — alimenta a série da instância em agentes sem heartbeat */
93
+ machine?: MachineSnapshot;
40
94
  }
41
95
  /** POST /v1/tasks/poll */
42
96
  interface PollRequest {
@@ -68,6 +122,16 @@ interface RunError {
68
122
  message: string;
69
123
  stack?: string;
70
124
  }
125
+ /**
126
+ * Progresso declarado pelo bot via `ctx.progress` — snapshot absoluto (o servidor
127
+ * sobrescreve, não soma). Opcional: sem ele a plataforma não mostra barra nem ETA.
128
+ */
129
+ interface RunProgress {
130
+ /** itens já processados (0..total) */
131
+ current: number;
132
+ /** total de itens previsto (>= 1) */
133
+ total: number;
134
+ }
71
135
  /** PATCH /v1/tasks/runs/{id} — 'running' pode repetir com logs incrementais */
72
136
  interface RunUpdateRequest {
73
137
  status: RunUpdateStatus;
@@ -75,6 +139,8 @@ interface RunUpdateRequest {
75
139
  error?: RunError;
76
140
  /** Delta desde o último envio — o servidor concatena */
77
141
  logs?: RunLogEntry[];
142
+ /** Snapshot mais recente de `ctx.progress` — pega carona no PATCH que sair (≤ 1/s) */
143
+ progress?: RunProgress;
78
144
  }
79
145
  interface RunUpdateResponse {
80
146
  ok: true;
@@ -248,6 +314,8 @@ interface HeartbeatPayload {
248
314
  timestamp: string;
249
315
  metadata?: Record<string, unknown>;
250
316
  release?: string;
317
+ /** Recursos da máquina no momento do beat (CPU, memória, disco, processo) */
318
+ machine?: MachineSnapshot;
251
319
  }
252
320
  type EnvelopeItem = {
253
321
  type: 'event';
@@ -269,4 +337,4 @@ interface Envelope {
269
337
  items: EnvelopeItem[];
270
338
  }
271
339
 
272
- export type { Breadcrumb, BreadcrumbLevel, Envelope, EnvelopeItem, EventPayload, EventType, EventUser, ExceptionData, HeartbeatPayload, InstanceInfo, PollRequest, PollResponse, RunAttachmentConfirmRequest, RunAttachmentConfirmResponse, RunAttachmentContentType, RunAttachmentCreateRequest, RunAttachmentCreateResponse, RunDescriptor, RunError, RunItemInput, RunItemStatus, RunItemsCreateRequest, RunItemsCreateResponse, RunLogEntry, RunLogLevel, RunStatus, RunUpdateRequest, RunUpdateResponse, RunUpdateStatus, StackFrame, StepData, StepStatus, TransactionPayload };
340
+ export type { Breadcrumb, BreadcrumbLevel, Envelope, EnvelopeItem, 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 };
@@ -1,3 +1,54 @@
1
+ /**
2
+ * Snapshot dos recursos da máquina onde a instância roda.
3
+ *
4
+ * Arquivo-folha do protocolo (não importa nada): reexportado por `hobots/protocol`
5
+ * e referenciado pelo heartbeat, pelo poll de tasks (`InstanceInfo.machine`) e por
6
+ * `contexts.machine` nos eventos. Bytes em bytes; percentuais 0..100. Campo ausente =
7
+ * não mensurável nesta plataforma/runtime.
8
+ */
9
+ interface MachineProcess {
10
+ pid: number;
11
+ /** segundos desde o `init()` do SDK (Python) / início do processo (Node) */
12
+ uptime_s: number;
13
+ /** memória residente do processo do bot */
14
+ rss_bytes?: number;
15
+ /** CPU do processo, 0..100 normalizado pelo número de cores (delta entre coletas); null até haver 2 amostras */
16
+ cpu_percent?: number | null;
17
+ /** só Node: `process.memoryUsage().heapUsed` */
18
+ heap_used_bytes?: number;
19
+ /** só Python: `threading.active_count()` */
20
+ threads?: number;
21
+ }
22
+ interface MachineSnapshot {
23
+ /** ISO 8601 — momento da coleta (relógio do cliente) */
24
+ sampled_at: string;
25
+ /** `InitOptions.instanceId` — liga o snapshot de um evento à série da instância */
26
+ instance_id?: string;
27
+ hostname?: string;
28
+ /** `process.platform` / `sys.platform`: 'linux' | 'win32' | 'darwin' | ... */
29
+ os?: string;
30
+ arch?: string;
31
+ /** cores lógicos */
32
+ cpu_count?: number;
33
+ /** uso de CPU do sistema, 0..100 — média entre duas coletas; null até haver 2 amostras */
34
+ cpu_percent?: number | null;
35
+ /** load average de 1 min (POSIX; ausente no Windows) */
36
+ load_1?: number;
37
+ mem_total_bytes?: number;
38
+ mem_available_bytes?: number;
39
+ /** (total - disponível) / total × 100 */
40
+ mem_percent?: number;
41
+ /** volume medido — o diretório de trabalho do processo */
42
+ disk_path?: string;
43
+ disk_total_bytes?: number;
44
+ /** disponível para o usuário (bavail) */
45
+ disk_free_bytes?: number;
46
+ disk_percent?: number;
47
+ process: MachineProcess;
48
+ /** de onde vieram os números */
49
+ source?: 'stdlib' | 'psutil' | 'stdlib-loadavg';
50
+ }
51
+
1
52
  /**
2
53
  * Tipos do protocolo de tasks do Hobots (agente SDK ↔ servidor).
3
54
  *
@@ -5,6 +56,7 @@
5
56
  * o app valida os payloads com zod e trava o drift via `satisfies z.ZodType<...>`
6
57
  * contra estes tipos (reexportados por `hobots/protocol`).
7
58
  */
59
+
8
60
  /** Ciclo de vida completo de uma run no servidor. */
9
61
  type RunStatus = 'pending' | 'claimed' | 'running' | 'succeeded' | 'failed' | 'canceled';
10
62
  /** Subconjunto que o agente pode enviar no PATCH. */
@@ -37,6 +89,8 @@ interface InstanceInfo {
37
89
  * agentes poll-only, que sem isso herdariam a tolerância do heartbeat (30s × 3).
38
90
  */
39
91
  poll_interval_ms?: number;
92
+ /** Recursos da máquina no momento do poll — alimenta a série da instância em agentes sem heartbeat */
93
+ machine?: MachineSnapshot;
40
94
  }
41
95
  /** POST /v1/tasks/poll */
42
96
  interface PollRequest {
@@ -68,6 +122,16 @@ interface RunError {
68
122
  message: string;
69
123
  stack?: string;
70
124
  }
125
+ /**
126
+ * Progresso declarado pelo bot via `ctx.progress` — snapshot absoluto (o servidor
127
+ * sobrescreve, não soma). Opcional: sem ele a plataforma não mostra barra nem ETA.
128
+ */
129
+ interface RunProgress {
130
+ /** itens já processados (0..total) */
131
+ current: number;
132
+ /** total de itens previsto (>= 1) */
133
+ total: number;
134
+ }
71
135
  /** PATCH /v1/tasks/runs/{id} — 'running' pode repetir com logs incrementais */
72
136
  interface RunUpdateRequest {
73
137
  status: RunUpdateStatus;
@@ -75,6 +139,8 @@ interface RunUpdateRequest {
75
139
  error?: RunError;
76
140
  /** Delta desde o último envio — o servidor concatena */
77
141
  logs?: RunLogEntry[];
142
+ /** Snapshot mais recente de `ctx.progress` — pega carona no PATCH que sair (≤ 1/s) */
143
+ progress?: RunProgress;
78
144
  }
79
145
  interface RunUpdateResponse {
80
146
  ok: true;
@@ -248,6 +314,8 @@ interface HeartbeatPayload {
248
314
  timestamp: string;
249
315
  metadata?: Record<string, unknown>;
250
316
  release?: string;
317
+ /** Recursos da máquina no momento do beat (CPU, memória, disco, processo) */
318
+ machine?: MachineSnapshot;
251
319
  }
252
320
  type EnvelopeItem = {
253
321
  type: 'event';
@@ -269,4 +337,4 @@ interface Envelope {
269
337
  items: EnvelopeItem[];
270
338
  }
271
339
 
272
- export type { Breadcrumb, BreadcrumbLevel, Envelope, EnvelopeItem, EventPayload, EventType, EventUser, ExceptionData, HeartbeatPayload, InstanceInfo, PollRequest, PollResponse, RunAttachmentConfirmRequest, RunAttachmentConfirmResponse, RunAttachmentContentType, RunAttachmentCreateRequest, RunAttachmentCreateResponse, RunDescriptor, RunError, RunItemInput, RunItemStatus, RunItemsCreateRequest, RunItemsCreateResponse, RunLogEntry, RunLogLevel, RunStatus, RunUpdateRequest, RunUpdateResponse, RunUpdateStatus, StackFrame, StepData, StepStatus, TransactionPayload };
340
+ export type { Breadcrumb, BreadcrumbLevel, Envelope, EnvelopeItem, 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 };
@@ -103,6 +103,7 @@ No poll, o agente se identifica com `InstanceInfo` (`hobots/protocol`): o campo
103
103
  | `tracesSampleRate` | `number` | `1` | amostragem de transactions (0..1) |
104
104
  | `maxBreadcrumbs` | `number` | `100` | tamanho do buffer de breadcrumbs |
105
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 |
106
107
  | `heartbeat` | `boolean \| HeartbeatOptions` | `false` | heartbeat periódico |
107
108
  | `tasks` | `boolean \| TasksOptions` | `false` | habilita a execução sob demanda (`true` usa defaults) |
108
109
  | `beforeSend` | `(event) => event \| null` | — | edita/descarta evento (retorne `null` para descartar) |
@@ -120,6 +121,8 @@ O teto da fila do transport (200 itens; ao encher, o **mais antigo** é descarta
120
121
  | `name` | `string` | o `instanceId` | nome amigável no dashboard |
121
122
  | `metadata` | `Record<string,unknown>` | — | metadados anexados ao batimento |
122
123
 
124
+ Com `machineMetrics` (default) cada batimento também leva `machine` — um [MachineSnapshot](./protocol.md#machinesnapshot) da máquina.
125
+
123
126
  ### CaptureHint
124
127
 
125
128
  | Campo | Tipo | Descrição |
@@ -169,6 +172,7 @@ interface RunContext {
169
172
  task: string;
170
173
  log: RunLogger; // ctx.log.debug/info/warn/error/success(message)
171
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)
172
176
  tx: Transaction; // transaction desta run — correlacionada e finalizada pelo SDK
173
177
  signal: AbortSignal;
174
178
  throwIfCanceled(): void; // lança RunCanceled se o signal já disparou
@@ -181,11 +185,20 @@ interface RunItems {
181
185
  failed(message: string, payload?: Record<string, unknown>, options?: { id?: string }): void;
182
186
  occurrence(message: string, payload?: Record<string, unknown>, options?: { id?: string }): void;
183
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
+ }
184
196
  ```
185
197
 
186
198
  - **`runId` / `task`** — identificam a execução; úteis para correlacionar com os seus próprios logs.
187
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).
188
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).
189
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.
190
203
  - **`signal`** — `AbortSignal` que dispara em cancelamento no app, timeout ou shutdown.
191
204
  - **`throwIfCanceled()`** — o ponto de parada de uma linha: lança `RunCanceled` se o `signal` já disparou.
package/docs/protocol.md CHANGED
@@ -44,6 +44,28 @@ Um `transaction` pode carregar `run_id`: é assim que a [transaction de uma run]
44
44
 
45
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).
46
46
 
47
+
48
+ ### MachineSnapshot
49
+
50
+ Recursos da máquina onde a instância roda. Vai em `HeartbeatPayload.machine`, em `instance.machine` do `POST /v1/tasks/poll` e em `contexts.machine` dos eventos. Bytes em bytes, percentuais 0..100; campo ausente = não mensurável nesta plataforma. Teto: **4 KiB** serializado (heartbeat/poll → 422; `contexts.machine` idem). Chaves desconhecidas são descartadas pelo servidor.
51
+
52
+ | Campo | Tipo | Descrição |
53
+ | --- | --- | --- |
54
+ | `sampled_at` | ISO 8601 | momento da coleta (relógio do cliente) — chave de dedupe da série |
55
+ | `instance_id` | string | o `instanceId` do `init()` — liga o snapshot de um evento à série da instância |
56
+ | `hostname`, `os`, `arch` | string | identificação da máquina (`process.platform` / `sys.platform`) |
57
+ | `cpu_count` | int | cores lógicos |
58
+ | `cpu_percent` | number \| null | uso de CPU do sistema — média entre duas coletas; `null` até haver 2 amostras |
59
+ | `load_1` | number | load average de 1 min (POSIX; ausente no Windows) |
60
+ | `mem_total_bytes`, `mem_available_bytes`, `mem_percent` | int, int, number | memória do sistema |
61
+ | `disk_path`, `disk_total_bytes`, `disk_free_bytes`, `disk_percent` | string, int, int, number | volume do diretório de trabalho do bot |
62
+ | `process.pid`, `process.uptime_s` | int, number | **obrigatórios** |
63
+ | `process.rss_bytes`, `process.cpu_percent` | int, number \| null | memória residente e CPU do processo (normalizada pelos cores) |
64
+ | `process.heap_used_bytes` / `process.threads` | int | só Node / só Python |
65
+ | `source` | `stdlib` \| `psutil` \| `stdlib-loadavg` | de onde vieram os números (`stdlib-loadavg` = CPU estimada pelo load, macOS sem psutil) |
66
+
67
+ O servidor guarda o último snapshot em `instances.machine` e um ponto por heartbeat/poll (máx. 1 a cada 30s por instância) em `instance_metrics`, expurgado em 14 dias.
68
+
47
69
  ## Tasks — polling e reporte
48
70
 
49
71
  Dois endpoints sob `<baseUrl>/v1/tasks`, ambos com `Authorization: Bearer <clientSecret>` (a credencial única do agente):
@@ -84,11 +106,13 @@ Corpo `RunUpdateRequest`:
84
106
  { "ts": "…", "level": "info", "message": "portal após o login",
85
107
  "attachments": ["<32 hex>"] } // opcional; ids de anexo (máx. 4)
86
108
  ],
87
- "error": { "type": "Error", "message": "…", "stack": "…" } // só com 'failed'
109
+ "error": { "type": "Error", "message": "…", "stack": "…" }, // só com 'failed'
110
+ "progress": { "current": 10, "total": 100 } // opcional (SDK ≥ 2.1): snapshot de ctx.progress
88
111
  }
89
112
  ```
90
113
 
91
114
  - `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).
115
+ - `progress` é **opcional** e aditivo (SDK ≥ 2.1): snapshot absoluto de `ctx.progress` (`current` 0..total, `total` ≥ 1) — o servidor **sobrescreve** `progress_current`/`progress_total` e grava `progress_started_at` no primeiro PATCH que o traga (relógio do servidor; âncora da ETA no app). `current > total` é clampado; progresso enviado a uma run já terminal é descartado. O SDK anexa o snapshot mais recente a **qualquer** PATCH que saia (logs, heartbeat, terminal) e só dispara um PATCH próprio, no máximo 1/s, quando nada mais está a caminho. Uma API anterior ao recurso descarta o campo (objeto não-strict).
92
116
  - `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.
93
117
  - Ordem garantida `running → logs → terminal` (PATCHes serializados numa cadeia de promise).
94
118
  - Retry `[1s, 4s, 16s]`; 4xx exceto 429 é terminal (**409** = run cancelada / transição inválida → marca a run como morta e dispara `ctx.signal`).
package/docs/tasks.md CHANGED
@@ -130,6 +130,44 @@ Diferente de logs e status, **uma run cancelada continua aceitando itens**: item
130
130
 
131
131
  > **Dados sensíveis:** como os logs, `message` e `payload` **são enviados ao servidor** e ficam visíveis na solicitação. Não inclua senhas, tokens ou dados pessoais desnecessários.
132
132
 
133
+ ## Progresso da execução (`ctx.progress`)
134
+
135
+ Itens contam **o que** foi entregue; progresso conta **quanto falta**. Quando a run percorre uma lista de tamanho conhecido, declare o total e avance a cada item — a solicitação no app mostra barra, percentual (`10/100 · 10%`) e **tempo restante estimado** (calculado pela taxa média desde o primeiro reporte).
136
+
137
+ ```js
138
+ Hobots.register('baixar-notas', async (params, ctx) => {
139
+ const notas = await listarNotas(params);
140
+ ctx.progress.total(notas.length); // antes do loop
141
+ for (const nota of notas) {
142
+ await baixar(nota);
143
+ ctx.progress.advance(); // a cada item
144
+ }
145
+ });
146
+ ```
147
+
148
+ ### API
149
+
150
+ ```ts
151
+ interface RunProgressReporter {
152
+ total(total: number): void; // declara/ajusta o total (inteiro >= 1; idempotente)
153
+ advance(count?: number): void; // incrementa os processados (padrão 1; clampa em total)
154
+ set(current: number, total?: number): void; // valor absoluto (ex.: retomada de um lote)
155
+ readonly current: number; // processados até agora
156
+ readonly snapshot: { current: number; total: number } | null; // null sem total declarado
157
+ }
158
+ ```
159
+
160
+ - **Opcional** — sem `total()` nada é enviado e a UI não mostra barra nem estimativa. `advance()` antes do `total()` só guarda o contador.
161
+ - **Independente de `ctx.items`** — use um, outro ou ambos; nenhum avança o outro (um item pode gerar vários registros, ou nenhum).
162
+ - **Total inválido** (`0`, negativo, não-inteiro) é ignorado com um aviso único no terminal.
163
+ - **Semântica de snapshot** — o servidor sobrescreve `current`/`total` com o último valor recebido; não há soma. Ajustar o `total` no meio da run (a lista cresceu) é permitido.
164
+
165
+ ### Como funciona (e por que não gera tráfego)
166
+
167
+ As chamadas são **síncronas e baratas**: o snapshot mais recente pega carona em **qualquer** PATCH que saia — logs incrementais, heartbeat de cancelamento, reporte terminal. Só quando nada mais está a caminho o SDK dispara um PATCH `running` próprio, no máximo **1 por segundo**. `advance()` num loop de 10.000 itens em 5 segundos gera ~5 PATCHes, não 10.000. O PATCH terminal (`succeeded`/`failed`) **sempre** leva o valor final — uma run que falhou aparece como `87/100`.
168
+
169
+ O servidor grava `progress_started_at` no primeiro PATCH com progresso (relógio do servidor) — é a âncora da estimativa de término, calculada no app: `restante = decorrido × (total − current) ÷ current`.
170
+
133
171
  ## Anexos (screenshots)
134
172
 
135
173
  Texto de log conta o que o robô fez; um print conta **como estava a tela**. Os métodos de captura — `Hobots.captureException(error, hint)` e `Hobots.captureMessage(message, level, hint)` — aceitam um **`attachment`** no hint. Dentro de uma execução de task, a imagem vira anexo da linha de log da run que espelha a captura, e aparece como miniatura logo abaixo dela na aba "logs do agente" da execução — clicável para ampliar.
package/docs/telemetry.md CHANGED
@@ -133,6 +133,17 @@ O primeiro batimento sai **na hora** em que o `init()` roda — não depois do p
133
133
 
134
134
  Essa identidade é a mesma que o poll de tasks envia (`agent.instance_id`) — os dois canais derivam do `instanceId` do `init()`, então o robô aparece como **um agente só** no módulo Agentes automaticamente. Por isso, com `tasks: true` o heartbeat é **opcional** (o poll já marca a presença do agente); ele é essencial para agentes só de telemetria.
135
135
 
136
+ ## Recursos da máquina (CPU, memória, disco)
137
+
138
+ Ligado por padrão em Node (`machineMetrics: true`), o SDK coleta CPU do sistema, memória, disco do volume do bot e RSS/CPU do próprio processo — **sem dependência nova** e sem timer próprio: a leitura pega carona em cada heartbeat e em cada poll de tasks (série da instância em **Instâncias → recursos da máquina**) e vai como `contexts.machine` em cada evento (card **"máquina no momento do erro"** no detalhe da issue). Assim dá para responder se o ambiente tem recurso suficiente e se um erro coincidiu com CPU/memória no limite.
139
+
140
+ ```js
141
+ Hobots.init({ clientSecret, instanceId: 'vm-01', heartbeat: true }); // coleta ligada
142
+ Hobots.init({ clientSecret, instanceId: 'vm-01', machineMetrics: false }); // desliga
143
+ ```
144
+
145
+ O snapshot tem ~600 bytes (teto do servidor: 4 KiB). Formato em [Protocolo → MachineSnapshot](./protocol.md#machinesnapshot).
146
+
136
147
  ## Flush e close
137
148
 
138
149
  Nada é enviado na hora: cada evento, transaction e heartbeat entra numa **fila em memória** e sai em lote. É por isso que essas duas funções existem — quando o processo morre, a fila morre com ele. Conceito: [Fila, flush e close](./concepts.md#fila-flush-e-close).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hobots",
3
- "version": "2.0.1",
3
+ "version": "2.3.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,
@@ -31,7 +31,8 @@
31
31
  "build": "tsup",
32
32
  "dev": "tsup --watch",
33
33
  "lint": "eslint . --max-warnings 0",
34
- "test": "node --test \"test/**/*.test.mjs\"",
34
+ "test": "vitest run",
35
+ "test:watch": "vitest",
35
36
  "typecheck": "tsc --noEmit"
36
37
  },
37
38
  "devDependencies": {
@@ -39,6 +40,7 @@
39
40
  "@workspace/eslint-config": "workspace:*",
40
41
  "@workspace/typescript-config": "workspace:*",
41
42
  "tsup": "^8.4.0",
42
- "typescript": "5.7.3"
43
+ "typescript": "5.7.3",
44
+ "vitest": "^4.1.10"
43
45
  }
44
46
  }