hobots 4.1.0 → 4.5.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 +49 -5
- package/dist/index.cjs +685 -110
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +101 -23
- package/dist/index.d.ts +101 -23
- package/dist/index.js +685 -110
- package/dist/index.js.map +1 -1
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.cts +50 -1
- package/dist/protocol.d.ts +50 -1
- package/docs/api-reference.md +9 -5
- package/docs/concepts.md +3 -3
- package/docs/getting-started.md +4 -2
- package/docs/protocol.md +21 -3
- package/docs/runs.md +41 -11
- package/package.json +1 -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 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} 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.transaction 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":[]}
|
|
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.transaction 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
|
@@ -165,6 +165,17 @@ interface RunCreateRequest {
|
|
|
165
165
|
}
|
|
166
166
|
/** Resposta (201): o mesmo descritor do poll, com params {}, form null e source 'agent'. */
|
|
167
167
|
type RunCreateResponse = RunDescriptor;
|
|
168
|
+
/**
|
|
169
|
+
* POST /v1/runs/:id/start — a run foi criada já reivindicada para esta instância
|
|
170
|
+
* (o Studio sobe o bot com HOBOTS_RUN_ID) e o processo se anexa a ela em vez de
|
|
171
|
+
* fazer poll. 409 = cancelada, já iniciada ou de outra instância; 404 = não existe.
|
|
172
|
+
*/
|
|
173
|
+
interface RunStartRequest {
|
|
174
|
+
/** presença da instância — a mesma do poll */
|
|
175
|
+
instance: InstanceInfo;
|
|
176
|
+
}
|
|
177
|
+
/** Resposta (200): o mesmo descritor do poll. */
|
|
178
|
+
type RunStartResponse = RunDescriptor;
|
|
168
179
|
interface RunError {
|
|
169
180
|
/** ex.: nome da classe do erro */
|
|
170
181
|
type?: string;
|
|
@@ -263,6 +274,44 @@ interface RunAttachmentConfirmRequest {
|
|
|
263
274
|
interface RunAttachmentConfirmResponse {
|
|
264
275
|
ok: true;
|
|
265
276
|
}
|
|
277
|
+
/**
|
|
278
|
+
* Onde o arquivo de um campo de upload está guardado:
|
|
279
|
+
* - 's3': enviado pela Central — objeto temporário no S3; o bot baixa por URL
|
|
280
|
+
* pré-assinada e confirma (DELETE), e o servidor apaga o objeto na hora.
|
|
281
|
+
* - 'local': escolhido no Studio, no MESMO computador do bot — fica em
|
|
282
|
+
* `$HOBOTS_LOCAL_FILES_DIR/<id>/<nome>` e nunca vai para a nuvem.
|
|
283
|
+
*/
|
|
284
|
+
type FileStorage = 's3' | 'local';
|
|
285
|
+
/**
|
|
286
|
+
* Valor de um campo de upload nos params da run (campo único) — ou um array deles
|
|
287
|
+
* (campo "vários arquivos"). Mesma forma do `FileRef` de `@workspace/task-forms`.
|
|
288
|
+
*/
|
|
289
|
+
interface FileRef {
|
|
290
|
+
/** id do arquivo na run (no storage 'local', um uuid — nome da pasta em HOBOTS_LOCAL_FILES_DIR) */
|
|
291
|
+
id: string;
|
|
292
|
+
/** nome original, como o usuário enviou */
|
|
293
|
+
name: string;
|
|
294
|
+
/** tamanho em bytes */
|
|
295
|
+
size: number;
|
|
296
|
+
contentType: string;
|
|
297
|
+
storage: FileStorage;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* POST /v1/runs/{id}/files/{fileId}/download (corpo vazio) — 200 com a URL pré-assinada
|
|
301
|
+
* de GET; 404 = arquivo não existe nesta run; 410 = já baixado (confirmado) ou expirado.
|
|
302
|
+
* Depois do GET completo o agente confirma com DELETE /v1/runs/{id}/files/{fileId}
|
|
303
|
+
* (204, idempotente), e o servidor apaga o objeto do S3.
|
|
304
|
+
*/
|
|
305
|
+
interface FileDownloadResponse {
|
|
306
|
+
/** URL pré-assinada de GET direto no S3 (não envie Authorization nesta requisição) */
|
|
307
|
+
url: string;
|
|
308
|
+
/** Validade da URL, em segundos */
|
|
309
|
+
expires_in: number;
|
|
310
|
+
name: string;
|
|
311
|
+
/** tamanho exato em bytes — o SDK confere o download contra ele */
|
|
312
|
+
size: number;
|
|
313
|
+
content_type: string;
|
|
314
|
+
}
|
|
266
315
|
|
|
267
316
|
var MAX_ITEMS_PER_ENVELOPE = 50;
|
|
268
317
|
var HEARTBEAT_DEFAULT_INTERVAL_MS = 30000;
|
|
@@ -441,4 +490,4 @@ interface Envelope {
|
|
|
441
490
|
items: EnvelopeItem[];
|
|
442
491
|
}
|
|
443
492
|
|
|
444
|
-
export { type Breadcrumb, type BreadcrumbLevel, type Envelope, type EnvelopeItem, type Environment, type EnvironmentInput, type EventPayload, type EventType, type EventUser, type ExceptionData, type HeartbeatPayload, type InstanceInfo, type InstanceMode, type ItemColumn, type ItemsDeclaration, type MachineProcess, type MachineSnapshot, type PollRequest, type PollResponse, type RunAttachmentConfirmRequest, type RunAttachmentConfirmResponse, type RunAttachmentContentType, type RunAttachmentCreateRequest, type RunAttachmentCreateResponse, type RunCreateRequest, type RunCreateResponse, type RunDescriptor, type RunError, type RunItemInput, type RunItemStatus, type RunItemsCreateRequest, type RunItemsCreateResponse, type RunLogEntry, type RunLogLevel, type RunProgress, type RunStatus, type RunUpdateRequest, type RunUpdateResponse, type RunUpdateStatus, type StackFrame, type StepData, type StepStatus, type TransactionPayload, WIRE_LIMITS, type WireLimits };
|
|
493
|
+
export { type Breadcrumb, type BreadcrumbLevel, type Envelope, type EnvelopeItem, type Environment, type EnvironmentInput, type EventPayload, type EventType, type EventUser, type ExceptionData, type FileDownloadResponse, type FileRef, type FileStorage, type HeartbeatPayload, type InstanceInfo, type InstanceMode, type ItemColumn, type ItemsDeclaration, type MachineProcess, type MachineSnapshot, type PollRequest, type PollResponse, type RunAttachmentConfirmRequest, type RunAttachmentConfirmResponse, type RunAttachmentContentType, type RunAttachmentCreateRequest, type RunAttachmentCreateResponse, type RunCreateRequest, type RunCreateResponse, type RunDescriptor, type RunError, type RunItemInput, type RunItemStatus, type RunItemsCreateRequest, type RunItemsCreateResponse, type RunLogEntry, type RunLogLevel, type RunProgress, type RunStartRequest, type RunStartResponse, type RunStatus, type RunUpdateRequest, type RunUpdateResponse, type RunUpdateStatus, type StackFrame, type StepData, type StepStatus, type TransactionPayload, WIRE_LIMITS, type WireLimits };
|
package/dist/protocol.d.ts
CHANGED
|
@@ -165,6 +165,17 @@ interface RunCreateRequest {
|
|
|
165
165
|
}
|
|
166
166
|
/** Resposta (201): o mesmo descritor do poll, com params {}, form null e source 'agent'. */
|
|
167
167
|
type RunCreateResponse = RunDescriptor;
|
|
168
|
+
/**
|
|
169
|
+
* POST /v1/runs/:id/start — a run foi criada já reivindicada para esta instância
|
|
170
|
+
* (o Studio sobe o bot com HOBOTS_RUN_ID) e o processo se anexa a ela em vez de
|
|
171
|
+
* fazer poll. 409 = cancelada, já iniciada ou de outra instância; 404 = não existe.
|
|
172
|
+
*/
|
|
173
|
+
interface RunStartRequest {
|
|
174
|
+
/** presença da instância — a mesma do poll */
|
|
175
|
+
instance: InstanceInfo;
|
|
176
|
+
}
|
|
177
|
+
/** Resposta (200): o mesmo descritor do poll. */
|
|
178
|
+
type RunStartResponse = RunDescriptor;
|
|
168
179
|
interface RunError {
|
|
169
180
|
/** ex.: nome da classe do erro */
|
|
170
181
|
type?: string;
|
|
@@ -263,6 +274,44 @@ interface RunAttachmentConfirmRequest {
|
|
|
263
274
|
interface RunAttachmentConfirmResponse {
|
|
264
275
|
ok: true;
|
|
265
276
|
}
|
|
277
|
+
/**
|
|
278
|
+
* Onde o arquivo de um campo de upload está guardado:
|
|
279
|
+
* - 's3': enviado pela Central — objeto temporário no S3; o bot baixa por URL
|
|
280
|
+
* pré-assinada e confirma (DELETE), e o servidor apaga o objeto na hora.
|
|
281
|
+
* - 'local': escolhido no Studio, no MESMO computador do bot — fica em
|
|
282
|
+
* `$HOBOTS_LOCAL_FILES_DIR/<id>/<nome>` e nunca vai para a nuvem.
|
|
283
|
+
*/
|
|
284
|
+
type FileStorage = 's3' | 'local';
|
|
285
|
+
/**
|
|
286
|
+
* Valor de um campo de upload nos params da run (campo único) — ou um array deles
|
|
287
|
+
* (campo "vários arquivos"). Mesma forma do `FileRef` de `@workspace/task-forms`.
|
|
288
|
+
*/
|
|
289
|
+
interface FileRef {
|
|
290
|
+
/** id do arquivo na run (no storage 'local', um uuid — nome da pasta em HOBOTS_LOCAL_FILES_DIR) */
|
|
291
|
+
id: string;
|
|
292
|
+
/** nome original, como o usuário enviou */
|
|
293
|
+
name: string;
|
|
294
|
+
/** tamanho em bytes */
|
|
295
|
+
size: number;
|
|
296
|
+
contentType: string;
|
|
297
|
+
storage: FileStorage;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* POST /v1/runs/{id}/files/{fileId}/download (corpo vazio) — 200 com a URL pré-assinada
|
|
301
|
+
* de GET; 404 = arquivo não existe nesta run; 410 = já baixado (confirmado) ou expirado.
|
|
302
|
+
* Depois do GET completo o agente confirma com DELETE /v1/runs/{id}/files/{fileId}
|
|
303
|
+
* (204, idempotente), e o servidor apaga o objeto do S3.
|
|
304
|
+
*/
|
|
305
|
+
interface FileDownloadResponse {
|
|
306
|
+
/** URL pré-assinada de GET direto no S3 (não envie Authorization nesta requisição) */
|
|
307
|
+
url: string;
|
|
308
|
+
/** Validade da URL, em segundos */
|
|
309
|
+
expires_in: number;
|
|
310
|
+
name: string;
|
|
311
|
+
/** tamanho exato em bytes — o SDK confere o download contra ele */
|
|
312
|
+
size: number;
|
|
313
|
+
content_type: string;
|
|
314
|
+
}
|
|
266
315
|
|
|
267
316
|
var MAX_ITEMS_PER_ENVELOPE = 50;
|
|
268
317
|
var HEARTBEAT_DEFAULT_INTERVAL_MS = 30000;
|
|
@@ -441,4 +490,4 @@ interface Envelope {
|
|
|
441
490
|
items: EnvelopeItem[];
|
|
442
491
|
}
|
|
443
492
|
|
|
444
|
-
export { type Breadcrumb, type BreadcrumbLevel, type Envelope, type EnvelopeItem, type Environment, type EnvironmentInput, type EventPayload, type EventType, type EventUser, type ExceptionData, type HeartbeatPayload, type InstanceInfo, type InstanceMode, type ItemColumn, type ItemsDeclaration, type MachineProcess, type MachineSnapshot, type PollRequest, type PollResponse, type RunAttachmentConfirmRequest, type RunAttachmentConfirmResponse, type RunAttachmentContentType, type RunAttachmentCreateRequest, type RunAttachmentCreateResponse, type RunCreateRequest, type RunCreateResponse, type RunDescriptor, type RunError, type RunItemInput, type RunItemStatus, type RunItemsCreateRequest, type RunItemsCreateResponse, type RunLogEntry, type RunLogLevel, type RunProgress, type RunStatus, type RunUpdateRequest, type RunUpdateResponse, type RunUpdateStatus, type StackFrame, type StepData, type StepStatus, type TransactionPayload, WIRE_LIMITS, type WireLimits };
|
|
493
|
+
export { type Breadcrumb, type BreadcrumbLevel, type Envelope, type EnvelopeItem, type Environment, type EnvironmentInput, type EventPayload, type EventType, type EventUser, type ExceptionData, type FileDownloadResponse, type FileRef, type FileStorage, type HeartbeatPayload, type InstanceInfo, type InstanceMode, type ItemColumn, type ItemsDeclaration, type MachineProcess, type MachineSnapshot, type PollRequest, type PollResponse, type RunAttachmentConfirmRequest, type RunAttachmentConfirmResponse, type RunAttachmentContentType, type RunAttachmentCreateRequest, type RunAttachmentCreateResponse, type RunCreateRequest, type RunCreateResponse, type RunDescriptor, type RunError, type RunItemInput, type RunItemStatus, type RunItemsCreateRequest, type RunItemsCreateResponse, type RunLogEntry, type RunLogLevel, type RunProgress, type RunStartRequest, type RunStartResponse, type RunStatus, type RunUpdateRequest, type RunUpdateResponse, type RunUpdateStatus, type StackFrame, type StepData, type StepStatus, type TransactionPayload, WIRE_LIMITS, type WireLimits };
|
package/docs/api-reference.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
| Import | Conteúdo |
|
|
6
6
|
| --- | --- |
|
|
7
|
-
| `hobots` | `hobots` (default e named), `register`, `start`, `close`, `captureException`, `retry`, `image` e os tipos. |
|
|
7
|
+
| `hobots` | `hobots` (default e named), `register`, `start`, `close`, `captureException`, `retry`, `image`, `isFileRef` e os tipos. |
|
|
8
8
|
| `hobots/protocol` | Tipos do wire format (telemetria e execuções). |
|
|
9
9
|
|
|
10
10
|
ESM e CommonJS (`const { hobots } = require('hobots')`), com `.d.ts`. Depois do `register()`/`start()`, `globalThis.hobots` também existe (tipado via `declare global`).
|
|
@@ -18,7 +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.flush` | `(timeoutMs = 15_000) => Promise<void>` | Envia **agora** o que a execução atual acumulou: steps 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. |
|
|
21
22
|
| `hobots.image` | `(input: AttachInput, options?: ImageOptions) => ImageAttachment` | Embrulha um print para ir junto de um log, item ou step. Puro, nunca lança. |
|
|
23
|
+
| `isFileRef` / `hobots.isFileRef` | `(value: unknown) => value is FileRef` | Reconhece um arquivo de campo de upload nos params. Puro. |
|
|
22
24
|
|
|
23
25
|
## Contexto da execução (`RunContext` — os membros de `hobots` e o `ctx` do handler)
|
|
24
26
|
|
|
@@ -29,14 +31,15 @@ ESM e CommonJS (`const { hobots } = require('hobots')`), com `.d.ts`. Depois do
|
|
|
29
31
|
| `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 …` |
|
|
30
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` + step em erro e o loop segue; `item: RunItemHandle` = `succeeded(payload?, { image }?)/failed(msg, payload?, { image }?)/occurrence(…)`, `id`, `index`, `step`, `reported` | Ponto de parada cooperativo (o `each` para só em cancelamento/timeout) |
|
|
31
33
|
| `progress` | `RunProgressReporter` — `total(n)`, `advance(count = 1)`, `set(current, total?)`, `current`, `snapshot` | `total/advance/set` são pontos de parada |
|
|
34
|
+
| `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) |
|
|
32
35
|
| `flush` | `(timeoutMs = 15_000) => Promise<void>` — envia já as steps raiz finalizadas (transactions parciais, op `hobots.run.flush`) e os itens/logs/progresso pendentes | Nunca lança |
|
|
33
|
-
| `transaction` | `Transaction` (`run`, op `hobots.run`) — `
|
|
36
|
+
| `transaction` | `Transaction` (`run`, op `hobots.run`) — `start(op?, description?)`, `succeeded(op?)` / `failed(err, op?, { message, image }?)` (fecham a step aberta mais recente — com aquele `op` quando informado; sem step aberta, undefined), `flush()`, `setTag`, `setStatus`, `setName`; `Step` — `start`, `setStatus('ok' \| 'warning' \| 'error', message?, { image }?)`, `failed(err, message?, { image }?)` (marca `error` e fecha; motivo em `data.error`, print em `data.attachments`, exibidos na Central), `setData(k, v)`, `succeeded()` (fecha mantendo o status) | Finalizada pelo SDK; `start` é ponto de parada |
|
|
34
37
|
| `signal` | `AbortSignal` | Cancelamento (app, timeout, shutdown) |
|
|
35
38
|
| `throwIfCanceled()` | `() => void` | Lança `RunCanceled` se o signal disparou |
|
|
36
39
|
| `sleep(ms)` | `(ms: number) => Promise<void>` | Rejeita com `RunCanceled` no cancelamento |
|
|
37
40
|
| `retry(fn, options?)` | como `hobots.retry` | |
|
|
38
41
|
|
|
39
|
-
`AttachInput = Uint8Array \| ArrayBuffer \| string` (caminho); `ImageOptions = { caption?, contentType? }`; `ImageInput = AttachInput \| ImageAttachment` é o que `log.*` (só o embrulho), `items.*` e `step.setStatus/
|
|
42
|
+
`AttachInput = Uint8Array \| ArrayBuffer \| string` (caminho); `ImageOptions = { caption?, contentType? }`; `ImageInput = AttachInput \| ImageAttachment` é o que `log.*` (só o embrulho), `items.*` e `step.setStatus/failed` aceitam. O upload (segundo plano, nunca bloqueia) acontece quando o evento é registrado.
|
|
40
43
|
|
|
41
44
|
Fora de uma execução, `hobots.*` cai em fallbacks seguros com aviso único por membro (log ecoa no terminal, imagem descartada; items/progress no-op; `signal` nunca aborta).
|
|
42
45
|
|
|
@@ -76,6 +79,7 @@ Só há uma chamada por processo, então todas as opções são **do agente**. O
|
|
|
76
79
|
| --- | --- | --- | --- |
|
|
77
80
|
| `concurrency` | `number` | `1` | execuções simultâneas do agente (1..5) |
|
|
78
81
|
| `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 |
|
|
79
83
|
|
|
80
84
|
`StartOptions = RunOptions`.
|
|
81
85
|
|
|
@@ -117,9 +121,9 @@ type RunHandler<P = Record<string, unknown>> = (params: P, ctx: RunContext) => v
|
|
|
117
121
|
| --- | --- |
|
|
118
122
|
| Heartbeat | 30 s (±10 % de jitter), sempre ligado, timer ref'd (segura o processo) |
|
|
119
123
|
| Flush da telemetria | 2 s ou 10 itens; envelopes de até 50 itens; fila de 200 |
|
|
120
|
-
| Retry HTTP (ingest, PATCH, itens, anexos) | 1 s / 4 s / 16 s com jitter; 4xx ≠ 429 é definitivo |
|
|
124
|
+
| Retry HTTP (ingest, PATCH, itens, anexos) | 1 s / 4 s / 16 s com jitter; 4xx ≠ 429 é definitivo; no registro de anexo, 429/501/503 (cota / anexos desabilitados) também são definitivos |
|
|
121
125
|
| Poll sem trabalho | `pollIntervalMs` (2 s); com trabalho, imediato (drain) |
|
|
122
|
-
| Poll/criação de run com erro | 1 s / 4 s / 16 s; 401 → 30 s com erro logado na 1ª e a cada 10 falhas |
|
|
126
|
+
| Poll/criação de run com erro | 1 s / 4 s / 16 s; 401 (Client Secret rejeitado) e 402 (agente fora do limite do plano) → 30 s com erro logado na 1ª e a cada 10 falhas |
|
|
123
127
|
| Logs da execução | flush a cada `logFlushIntervalMs` (2 s) ou 20 linhas; chunks de 100; 1000 por execução no servidor |
|
|
124
128
|
| Cancel-watch / sinal de vida | `cancelCheckIntervalMs` (5 s); o servidor marca `AgentTerminated` após ~120 s sem PATCH |
|
|
125
129
|
| Retry do handler | `retries` 1, `retryDelayMs` 5 s |
|
package/docs/concepts.md
CHANGED
|
@@ -19,7 +19,7 @@ Defaults numéricos vivem em [Tempos e intervalos](./api-reference.md#tempos-e-i
|
|
|
19
19
|
| [Cancelamento cooperativo](#cancelamento-cooperativo) | O app pede, o handler para no próximo ponto de parada | Automático em items/progress/steps/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
|
-
| [Transaction](#transaction) e [Step](#step) | Quanto demorou e onde o tempo foi | `hobots.transaction.
|
|
22
|
+
| [Transaction](#transaction) e [Step](#step) | Quanto demorou e onde o tempo foi | `hobots.transaction.start` |
|
|
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 |
|
|
@@ -31,7 +31,7 @@ Defaults numéricos vivem em [Tempos e intervalos](./api-reference.md#tempos-e-i
|
|
|
31
31
|
|
|
32
32
|
| No SDK | No app |
|
|
33
33
|
| --- | --- |
|
|
34
|
-
| **Agente** — dono do Client Secret (`hb_…`) que autentica tudo; o que ele faz é o handler de `register()`/`start()` | **Configurações →
|
|
34
|
+
| **Agente** — dono do Client Secret (`hb_…`) que autentica tudo; o que ele faz é o handler de `register()`/`start()` | No agente, **Configurações**: aba **agente → credenciais** (Client Secret) e **formulário** |
|
|
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** |
|
|
@@ -95,7 +95,7 @@ Uma **transaction** é uma unidade de trabalho **medida**: começa, termina, e o
|
|
|
95
95
|
|
|
96
96
|
## Step
|
|
97
97
|
|
|
98
|
-
Uma **step** é uma etapa medida dentro da transaction. A transaction diz que a execução levou 4 minutos; as steps dizem que 3min40 foram esperando o portal. `hobots.transaction.
|
|
98
|
+
Uma **step** é uma etapa medida dentro da transaction. A transaction diz que a execução levou 4 minutos; as steps dizem que 3min40 foram esperando o portal. `hobots.transaction.start(op, descrição)` abre uma; `step.start` aninha; `setData` anexa números; `succeeded()` fecha e `failed(erro)` marca erro e fecha — ou, sem guardar a step, `hobots.transaction.succeeded(op?)`/`failed(erro, op?)` fecham a aberta mais recente (com aquele `op`) (steps abertas são fechadas com a transaction). Status `ok`, `warning` (passou com ressalva) ou `error`. No app isso vira o **waterfall** da execução. Abrir uma step é um ponto de parada cooperativo.
|
|
99
99
|
|
|
100
100
|
## Heartbeat
|
|
101
101
|
|
package/docs/getting-started.md
CHANGED
|
@@ -21,7 +21,7 @@ Sem dependências de runtime. Requer **Node 18+** (usa o `fetch` global).
|
|
|
21
21
|
|
|
22
22
|
## Credenciais
|
|
23
23
|
|
|
24
|
-
Cada agente tem **um Client Secret** (`hb_<64 hex>`), copiado em **Configurações →
|
|
24
|
+
Cada agente tem **um Client Secret** (`hb_<64 hex>`), copiado na Central em **agente → Configurações → agente → aba credenciais**. O SDK lê do ambiente:
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
27
|
export HOBOTS_CLIENT_SECRET=hb_<64 hex>
|
|
@@ -30,9 +30,11 @@ export HOBOTS_INSTANCE_ID=vm-cliente-01 # opcional — sem ela, o hostname
|
|
|
30
30
|
|
|
31
31
|
Também dá para passar `clientSecret`/`instanceId` nas opções de `register()`/`start()` (o snippet do app já vem assim). Em desenvolvimento, `HOBOTS_API_URL=http://localhost:3001` aponta para outra API; em produção é sempre `https://api2.hobots.app`.
|
|
32
32
|
|
|
33
|
+
As envs também podem ficar num `.env` na pasta de onde o bot roda: o SDK carrega o arquivo sozinho no import (sem `dotenv`); a env já definida no ambiente vence o arquivo.
|
|
34
|
+
|
|
33
35
|
## Primeiro agente sob demanda (`register`)
|
|
34
36
|
|
|
35
|
-
1. No app, abra o agente em **Configurações →
|
|
37
|
+
1. No app, abra o agente em **Configurações → formulário** e monte o **formulário do agente** (os campos que cada solicitação vai carregar).
|
|
36
38
|
2. No robô:
|
|
37
39
|
|
|
38
40
|
```js
|
package/docs/protocol.md
CHANGED
|
@@ -96,7 +96,7 @@ Resposta `PollResponse` = `{ runs: RunDescriptor[] }`, cada `{ id, params, form,
|
|
|
96
96
|
|
|
97
97
|
O `instance.instance_id` é a **mesma identidade** do heartbeat de telemetria (`instance_id`): o servidor correlaciona os dois canais numa tabela `instances` por `(agent_id, instance_id)`. Os dois derivam do mesmo `instanceId` — o robô aparece como um agente só no painel. O próprio poll também marca a presença da instância (`last_poll_at`). Um processo no modo `start()` nunca faz poll.
|
|
98
98
|
|
|
99
|
-
**Backoff:** um request por poll; o loop decide o ritmo. HTTP 401 (key rejeitada) → retry lento a 30s com log throttled; outros erros → ladder `[1s, 4s, 16s]`.
|
|
99
|
+
**Backoff:** um request por poll; o loop decide o ritmo. HTTP 401 (key rejeitada) e 402 (agente fora do limite do plano) → retry lento a 30s com log throttled; outros erros → ladder `[1s, 4s, 16s]`.
|
|
100
100
|
|
|
101
101
|
### `POST /v1/runs`
|
|
102
102
|
|
|
@@ -106,6 +106,14 @@ Modo `start`: a instância abre a **própria run**. Corpo `RunCreateRequest` —
|
|
|
106
106
|
- `touchInstance` roda antes do insert (presença); se falhar, a API responde `503` e o SDK insiste com o ladder do poll (o handler só começa quando a run existe). Corpo vazio ou sem `instance.instance_id` → `422`.
|
|
107
107
|
- Sem dedupe: um processo reiniciado em menos de 120 s deixa a run anterior `running` até o sweep marcá-la `AgentTerminated`. O app não oferece "reexecutar" para runs `agent` — ninguém faria poll por elas.
|
|
108
108
|
|
|
109
|
+
### `POST /v1/runs/{id}/start`
|
|
110
|
+
|
|
111
|
+
`register()` com env `HOBOTS_RUN_ID` (ou a opção `runId`): a run já foi criada **reivindicada para esta instância** (o Studio executa o bot sob demanda sem que ele faça poll) e o processo se anexa a ela. Corpo `RunStartRequest` — `{ "instance": { /* InstanceInfo, o mesmo do poll */ } }`. Resposta `200` `RunStartResponse` = o mesmo `RunDescriptor` do poll.
|
|
112
|
+
|
|
113
|
+
- `404` = a run não existe; `409` = cancelada, já iniciada ou de outra instância. Os dois (e qualquer outro 4xx ≠ 429) encerram o SDK **sem** rodar o handler, com o motivo no log.
|
|
114
|
+
- Rede, timeout, `5xx` e `429`: até 4 tentativas (esperas de 1 s/4 s/16 s com jitter); esgotadas, o SDK desiste e encerra.
|
|
115
|
+
- Dali em diante a run é reportada pelas rotas abaixo; terminada, o SDK faz `close()` e o processo sai.
|
|
116
|
+
|
|
109
117
|
### `PATCH /v1/runs/{id}`
|
|
110
118
|
|
|
111
119
|
Corpo `RunUpdateRequest`:
|
|
@@ -135,7 +143,7 @@ Ciclo de vida completo da run no servidor (`RunStatus`): `pending → claimed
|
|
|
135
143
|
|
|
136
144
|
### `POST /v1/runs/{id}/items`
|
|
137
145
|
|
|
138
|
-
Registra em lote os **itens de execução** (a entrega do bot — `hobots.items` no SDK).
|
|
146
|
+
Registra em lote os **itens de execução** (a entrega do bot — `hobots.items` no SDK). Upsert por `(run, id)`: um `id` repetido na mesma run atualiza o item (vale o último reporte — o bot que tenta de novo corrige `failed` → `succeeded`); reenvio idêntico (retry de rede) não mexe nos contadores.
|
|
139
147
|
|
|
140
148
|
Corpo `RunItemsCreateRequest`:
|
|
141
149
|
|
|
@@ -161,7 +169,7 @@ Resposta `200` `RunItemsCreateResponse`: `{ "accepted": 1, "duplicates": 0 }`
|
|
|
161
169
|
|
|
162
170
|
- **Run cancelada/terminal aceita itens** (200): item é fato de trabalho já feito — mesmo racional do log tardio. Esta rota não tem 409.
|
|
163
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.
|
|
164
|
-
- Retry `[1s, 4s, 16s]` para rede/5xx; `404` = servidor sem o recurso (o SDK degrada em silêncio); `402` = armazenamento do plano esgotado. Não há teto de itens por run.
|
|
172
|
+
- Retry `[1s, 4s, 16s]` para rede/5xx; `404` = servidor sem o recurso (o SDK degrada em silêncio); `402` = armazenamento do plano esgotado (sem retry: o SDK descarta o lote com um aviso por episódio e segue enviando os próximos). Não há teto de itens por run.
|
|
165
173
|
|
|
166
174
|
| Status | Quando |
|
|
167
175
|
| --- | --- |
|
|
@@ -220,6 +228,16 @@ Confirma o upload. Corpo `RunAttachmentConfirmRequest`: `{ "status": "uploaded"
|
|
|
220
228
|
|
|
221
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 step).
|
|
222
230
|
|
|
231
|
+
### `POST /v1/runs/{id}/files/{fileId}/download`
|
|
232
|
+
|
|
233
|
+
Arquivo de campo de upload do formulário com `storage: 's3'` (o valor do campo nos params é um `FileRef` `{ id, name, size, contentType, storage }`, ou uma lista). Corpo vazio. Resposta `FileDownloadResponse`: `{ "url", "expires_in", "name", "size", "content_type" }` — `url` é um GET pré-assinado direto no S3 (o SDK **não** manda `Authorization` nele). `404` = o arquivo não existe nesta execução; `410` = já baixado (confirmado) ou expirado.
|
|
234
|
+
|
|
235
|
+
### `DELETE /v1/runs/{id}/files/{fileId}`
|
|
236
|
+
|
|
237
|
+
Confirma o download: o servidor apaga o objeto do S3 na hora. `204`, idempotente. O SDK só chama depois do GET completo e do tamanho conferido; `404`/`410` aqui contam como confirmado.
|
|
238
|
+
|
|
239
|
+
Arquivos com `storage: 'local'` (escolhidos no Studio) não passam pela API: ficam em `$HOBOTS_LOCAL_FILES_DIR/<id>/<nome>` no computador do bot.
|
|
240
|
+
|
|
223
241
|
## Onde vive o claim atômico
|
|
224
242
|
|
|
225
243
|
O `POST /v1/runs/poll` no app chama `claimPendingRuns()` em
|
package/docs/runs.md
CHANGED
|
@@ -29,9 +29,9 @@ 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 step = hobots.transaction.
|
|
32
|
+
const step = hobots.transaction.start('http.client', `GET /notas/${nota}`);
|
|
33
33
|
const xml = await portal.baixar(nota);
|
|
34
|
-
step.setData('bytes', xml.length).
|
|
34
|
+
step.setData('bytes', xml.length).succeeded();
|
|
35
35
|
hobots.items.succeeded({ numero_nota: nota }, { id: nota });
|
|
36
36
|
}
|
|
37
37
|
```
|
|
@@ -43,7 +43,7 @@ Isso funciona com `concurrency > 1` porque cada execução roda no seu contexto
|
|
|
43
43
|
| `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
44
|
| `hobots.items` | Ver [Itens](#itens-de-execução-hobotsitems) |
|
|
45
45
|
| `hobots.progress` | Ver [Progresso](#progresso-hobotsprogress) |
|
|
46
|
-
| `hobots.transaction` | Transaction da execução (`run`), já correlacionada e finalizada pelo SDK. `
|
|
46
|
+
| `hobots.transaction` | Transaction da execução (`run`), já correlacionada e finalizada pelo SDK. `start(op, descrição)` = uma step no waterfall. |
|
|
47
47
|
| `hobots.image(imagem, opts?)` | Ver [Imagens](#imagens-screenshots) |
|
|
48
48
|
| `hobots.captureException(erro, hint?)` | Issue no módulo Issues; `hint.attachment` anexa um print à linha de log |
|
|
49
49
|
| `hobots.retry(fn, opts?)` | Ver [Retry](#retry) |
|
|
@@ -90,8 +90,8 @@ const resultado = await hobots.items.each(
|
|
|
90
90
|
// resultado = { total, succeeded, failed, occurrence }
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
-
- Cada elemento vira uma **step** no waterfall (`op` 'item', descrição = `id`; `item.step.
|
|
94
|
-
- **Exceção no handler** = item `failed` com a mensagem do erro, `step.
|
|
93
|
+
- Cada elemento vira uma **step** no waterfall (`op` 'item', descrição = `id`; `item.step.start(...)` mede sub-etapas) e um **item** com o `id` e o payload base.
|
|
94
|
+
- **Exceção no handler** = item `failed` com a mensagem do erro, `step.failed(erro)`, 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.
|
|
95
95
|
- **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
96
|
- 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
97
|
- Aceita `Iterable` ou `AsyncIterable` (sem `length`, nenhum total é declarado).
|
|
@@ -125,11 +125,11 @@ hobots.start(async () => {
|
|
|
125
125
|
while (true) {
|
|
126
126
|
const lote = await fila.pegar();
|
|
127
127
|
if (lote.length) {
|
|
128
|
-
const rodada = hobots.transaction.
|
|
128
|
+
const rodada = hobots.transaction.start('rodada', `${lote.length} pedido(s)`);
|
|
129
129
|
try {
|
|
130
130
|
for (const pedido of lote) await processar(pedido, rodada); // steps filhas + hobots.items.*
|
|
131
131
|
} finally {
|
|
132
|
-
rodada.
|
|
132
|
+
rodada.succeeded();
|
|
133
133
|
await hobots.flush(); // waterfall e itens desta rodada já na Central
|
|
134
134
|
}
|
|
135
135
|
}
|
|
@@ -152,6 +152,36 @@ for (const nota of notas) {
|
|
|
152
152
|
|
|
153
153
|
Opcional e independente de `items`. Sem `total()` nada é enviado. Snapshot absoluto que pega carona nos PATCHes de log (no pior caso 1/s); o PATCH terminal leva o valor final. `set(atual, total?)`, `current` e `snapshot` completam a API. `total/advance/set` são pontos de parada cooperativos; `current`/`snapshot` nunca lançam.
|
|
154
154
|
|
|
155
|
+
## Arquivos do formulário (`hobots.files`)
|
|
156
|
+
|
|
157
|
+
Um campo de upload do formulário chega nos `params` como um `FileRef` — `{ id, name, size, contentType, storage }` — ou como uma lista deles (campo "vários arquivos"). `hobots.files` traz o arquivo para o disco desta máquina:
|
|
158
|
+
|
|
159
|
+
```js
|
|
160
|
+
const caminho = await hobots.files.download('planilha'); // campo único
|
|
161
|
+
const caminhos = await hobots.files.downloadAll('notas', { dest: './entrada' }); // lista
|
|
162
|
+
const bytes = await hobots.files.read('contrato'); // Buffer em memória
|
|
163
|
+
for (const ref of hobots.files.list()) hobots.log.info(ref.name, ref.size);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
| Método | Retorno | Notas |
|
|
167
|
+
| --- | --- | --- |
|
|
168
|
+
| `list()` | `FileRef[]` | Todos os arquivos dos params (campos de topo, valor único ou lista), na ordem dos campos. |
|
|
169
|
+
| `get(campo)` | `FileRef \| FileRef[] \| undefined` | `undefined` se o campo não tem arquivo. |
|
|
170
|
+
| `download(refOuCampo, { dest? })` | `Promise<string>` | Caminho local. Campo com vários arquivos lança — use `downloadAll` ou passe um item. |
|
|
171
|
+
| `downloadAll(campo, { dest? })` | `Promise<string[]>` | Em ordem; aqui `dest` é sempre uma pasta. |
|
|
172
|
+
| `read(refOuCampo)` | `Promise<Buffer>` | Baixa, lê e apaga a cópia. |
|
|
173
|
+
|
|
174
|
+
**Origem consumida.** O arquivo é baixado **uma vez**:
|
|
175
|
+
|
|
176
|
+
- `storage: 's3'` (enviado pela Central): o SDK pede a URL pré-assinada, baixa em streaming para um `.part`, confere o tamanho contra o anunciado e só então confirma — o servidor apaga o objeto do S3 na hora. Uma falha (rede, tamanho divergente, 60 s sem bytes novos, cancelamento) apaga o `.part`, **não confirma** e o download pode ser tentado de novo. Depois de confirmado, um novo download lança `TasksApiError` 410 `arquivo "x" já baixado ou expirado`; arquivo que não existe na execução, 404 `arquivo "x" não encontrado nesta execução`. Se a confirmação falhar (o arquivo já está no disco), o download dá certo e sai um aviso no log da execução — o servidor apaga o objeto quando ele expirar.
|
|
177
|
+
- `storage: 'local'` (escolhido no Studio): o arquivo **nunca sai do computador**. O Studio o deixa em `$HOBOTS_LOCAL_FILES_DIR/<id>/<nome>`; o SDK confere que o id é um uuid e que a pasta resolvida fica dentro do diretório, copia o único arquivo regular dela (links simbólicos não contam), confere o tamanho e apaga a pasta de origem. Sem a env ou sem a pasta (bot em outro computador, arquivo já consumido): `arquivo "x" enviado pelo Studio de outro computador ou já consumido`.
|
|
178
|
+
|
|
179
|
+
**Destino.** Sem `dest`, a cópia vai para uma subpasta própria dentro da pasta temporária da execução, **apagada quando a execução termina** (arquivo de cliente não fica esquecido no disco). `dest` que é uma pasta existente — ou termina em `/` — recebe o arquivo com o nome original (só o último segmento, sem caracteres proibidos no Windows); qualquer outro caminho é o arquivo final (as pastas intermediárias são criadas).
|
|
180
|
+
|
|
181
|
+
**Cancelamento.** `download`/`downloadAll`/`read` respeitam `hobots.signal`: cancelada a execução, lançam `RunCanceled` e o arquivo não é consumido. Fora de uma execução, `list()` é vazio e os downloads rejeitam.
|
|
182
|
+
|
|
183
|
+
`isFileRef(valor)` (export do pacote, também `hobots.isFileRef`) reconhece a forma de um `FileRef` — útil para percorrer params aninhados.
|
|
184
|
+
|
|
155
185
|
## Imagens (screenshots)
|
|
156
186
|
|
|
157
187
|
Um print vai **junto do evento** que ele ilustra — a linha de log, o item ou a step — para o
|
|
@@ -170,14 +200,14 @@ await hobots.items.each(notas, async (nota, item) => {
|
|
|
170
200
|
});
|
|
171
201
|
|
|
172
202
|
// na step
|
|
173
|
-
const step = hobots.transaction.
|
|
203
|
+
const step = hobots.transaction.start('emitir', numero);
|
|
174
204
|
try {
|
|
175
205
|
await emitir(numero);
|
|
176
206
|
} catch (erro) {
|
|
177
|
-
step.
|
|
207
|
+
step.failed(erro, undefined, { image: await page.screenshot() });
|
|
178
208
|
throw erro;
|
|
179
209
|
} finally {
|
|
180
|
-
step.
|
|
210
|
+
step.succeeded();
|
|
181
211
|
}
|
|
182
212
|
|
|
183
213
|
// numa issue (módulo Issues) — o print vai na linha de log que espelha o erro
|
|
@@ -190,7 +220,7 @@ O SDK não tira o print: aceita bytes (`Uint8Array`/`ArrayBuffer`/`Buffer`) ou u
|
|
|
190
220
|
|
|
191
221
|
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`:
|
|
192
222
|
|
|
193
|
-
- `hobots.items.*`, `hobots.progress.total/advance/set`, `hobots.transaction.
|
|
223
|
+
- `hobots.items.*`, `hobots.progress.total/advance/set`, `hobots.transaction.start()`, `hobots.sleep()` e cada tentativa de `hobots.retry()`.
|
|
194
224
|
- **Nunca `hobots.log.*`** — log em `finally` de limpeza não pode explodir.
|
|
195
225
|
|
|
196
226
|
```js
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hobots",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.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,
|