hobots 1.9.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/index.cjs +230 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +39 -6
- package/dist/index.d.ts +39 -6
- package/dist/index.js +230 -13
- package/dist/index.js.map +1 -1
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.cts +41 -3
- package/dist/protocol.d.ts +41 -3
- package/docs/api-reference.md +12 -1
- package/docs/concepts.md +2 -2
- package/docs/protocol.md +38 -3
- package/docs/tasks.md +62 -0
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Breadcrumb, EventUser, Severity, EventPayload, TransactionPayload, HeartbeatPayload, StepStatus, RunLogLevel, StepData, RunAttachmentContentType } from './protocol.cjs';
|
|
2
|
-
export {
|
|
1
|
+
import { Breadcrumb, EventUser, Severity, EventPayload, TransactionPayload, HeartbeatPayload, StepStatus, RunLogLevel, StepData, RunItemStatus, RunAttachmentContentType } from './protocol.cjs';
|
|
2
|
+
export { Envelope, EnvelopeItem, ExceptionData, InstanceInfo, PollRequest, PollResponse, RunAttachmentConfirmRequest, RunAttachmentConfirmResponse, RunAttachmentCreateRequest, RunAttachmentCreateResponse, RunDescriptor, RunError, RunItemInput, RunItemsCreateRequest, RunItemsCreateResponse, RunLogEntry, RunStatus, RunUpdateRequest, RunUpdateResponse, RunUpdateStatus, StackFrame } from './protocol.cjs';
|
|
3
3
|
|
|
4
4
|
/** Aceita tudo que o handler pode ter em mãos: bytes ou um caminho de arquivo (só Node). */
|
|
5
5
|
type AttachInput = Uint8Array | ArrayBuffer | string;
|
|
@@ -50,6 +50,8 @@ interface Logger {
|
|
|
50
50
|
declare class Client {
|
|
51
51
|
#private;
|
|
52
52
|
readonly logger: Logger;
|
|
53
|
+
/** Eco de lifecycle (Transaction.emit) e afins só imprimem no terminal com debug ligado. */
|
|
54
|
+
readonly debug: boolean;
|
|
53
55
|
private readonly transport;
|
|
54
56
|
constructor(options: InitOptions, config?: ResolvedConfig);
|
|
55
57
|
get options(): InitOptions;
|
|
@@ -110,9 +112,10 @@ declare class Transaction {
|
|
|
110
112
|
/** Correlaciona a uma execução de task — preenchido automaticamente em ctx.tx */
|
|
111
113
|
runId?: string | undefined);
|
|
112
114
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
115
|
+
* Registra o início/fim de uma step ou transaction: espelha a linha no sink de
|
|
116
|
+
* lifecycle, se conectado (é assim que os eventos de performance aparecem nos
|
|
117
|
+
* logs da run no app), e ecoa no terminal apenas com `debug: true` — o SDK não
|
|
118
|
+
* escreve no stdout do processo do usuário sem permissão.
|
|
116
119
|
*/
|
|
117
120
|
emit(kind: 'transaction' | 'step', phase: 'start' | 'finish', label: string, startTs?: string, endTs?: string, status?: StepStatus | string): void;
|
|
118
121
|
/** Conecta o espelho de lifecycle (RunReporter) e faz replay do backlog em ordem. */
|
|
@@ -167,11 +170,41 @@ interface RunLogger {
|
|
|
167
170
|
error(message: string): void;
|
|
168
171
|
success(message: string): void;
|
|
169
172
|
}
|
|
173
|
+
interface RunItemOptions {
|
|
174
|
+
/**
|
|
175
|
+
* Identidade de negócio do item na run (ex.: nº da nota fiscal). Repetido na
|
|
176
|
+
* mesma run = ignorado pelo servidor (registro único, retry idempotente).
|
|
177
|
+
* Omitido: o SDK gera um id único e cada chamada vira um item novo.
|
|
178
|
+
*/
|
|
179
|
+
id?: string;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Registro dos itens que a execução processou/gerou (a entrega do bot: notas
|
|
183
|
+
* fiscais baixadas, boletos emitidos...). Chamadas são síncronas — os itens são
|
|
184
|
+
* bufferizados e enviados em lotes; o agente drena o buffer antes do reporte final.
|
|
185
|
+
*/
|
|
186
|
+
interface RunItems {
|
|
187
|
+
/** Forma genérica — os atalhos succeeded/failed/occurrence cobrem o dia a dia. */
|
|
188
|
+
add(item: {
|
|
189
|
+
status: RunItemStatus;
|
|
190
|
+
id?: string;
|
|
191
|
+
message?: string;
|
|
192
|
+
payload?: Record<string, unknown>;
|
|
193
|
+
}): void;
|
|
194
|
+
/** Item processado com sucesso. `payload` = atributos do item (viram colunas na plataforma). */
|
|
195
|
+
succeeded(payload?: Record<string, unknown>, options?: RunItemOptions): void;
|
|
196
|
+
/** Falha técnica ao processar o item — `message` é o motivo exibido na plataforma. */
|
|
197
|
+
failed(message: string, payload?: Record<string, unknown>, options?: RunItemOptions): void;
|
|
198
|
+
/** Ocorrência de negócio (ex.: "nota nº 10 não encontrada") — não é falha do bot. */
|
|
199
|
+
occurrence(message: string, payload?: Record<string, unknown>, options?: RunItemOptions): void;
|
|
200
|
+
}
|
|
170
201
|
interface RunContext {
|
|
171
202
|
runId: string;
|
|
172
203
|
task: string;
|
|
173
204
|
/** Log bufferizado — enviado ao servidor em PATCHes incrementais com throttle */
|
|
174
205
|
log: RunLogger;
|
|
206
|
+
/** Itens processados/gerados pela execução — a entrega do bot, com status e atributos */
|
|
207
|
+
items: RunItems;
|
|
175
208
|
/**
|
|
176
209
|
* Transaction de performance desta execução — já correlacionada à run e finalizada
|
|
177
210
|
* automaticamente ao fim do handler. Use `ctx.tx.startChild(op, description)` pra medir
|
|
@@ -303,4 +336,4 @@ declare function close(timeoutMs?: number): Promise<boolean>;
|
|
|
303
336
|
|
|
304
337
|
declare function scrubSecrets(text: string): string;
|
|
305
338
|
|
|
306
|
-
export { type AttachInput, Breadcrumb, type BreadcrumbInput, type CaptureAttachment, type CaptureHint, Client, DEFAULT_BASE_URL, EventPayload, EventUser, type HeartbeatOptions, HeartbeatPayload, type InitOptions, RunAttachmentContentType, type RunContext, RunLogLevel, Scope, Severity, Step, StepData, type TaskHandler, type TasksOptions, Transaction, TransactionPayload, addBreadcrumb, captureException, captureMessage, close, flush, getClient, init, register, scrubSecrets, setContext, setExtra, setTag, setTags, setUser, start, startTransaction, stop, withScope };
|
|
339
|
+
export { type AttachInput, Breadcrumb, type BreadcrumbInput, type CaptureAttachment, type CaptureHint, Client, DEFAULT_BASE_URL, EventPayload, EventUser, type HeartbeatOptions, HeartbeatPayload, type InitOptions, RunAttachmentContentType, type RunContext, type RunItemOptions, RunItemStatus, type RunItems, RunLogLevel, Scope, Severity, Step, StepData, type TaskHandler, type TasksOptions, Transaction, TransactionPayload, addBreadcrumb, captureException, captureMessage, close, flush, getClient, init, register, scrubSecrets, setContext, setExtra, setTag, setTags, setUser, start, startTransaction, stop, withScope };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Breadcrumb, EventUser, Severity, EventPayload, TransactionPayload, HeartbeatPayload, StepStatus, RunLogLevel, StepData, RunAttachmentContentType } from './protocol.js';
|
|
2
|
-
export {
|
|
1
|
+
import { Breadcrumb, EventUser, Severity, EventPayload, TransactionPayload, HeartbeatPayload, StepStatus, RunLogLevel, StepData, RunItemStatus, RunAttachmentContentType } from './protocol.js';
|
|
2
|
+
export { Envelope, EnvelopeItem, ExceptionData, InstanceInfo, PollRequest, PollResponse, RunAttachmentConfirmRequest, RunAttachmentConfirmResponse, RunAttachmentCreateRequest, RunAttachmentCreateResponse, RunDescriptor, RunError, RunItemInput, RunItemsCreateRequest, RunItemsCreateResponse, RunLogEntry, RunStatus, RunUpdateRequest, RunUpdateResponse, RunUpdateStatus, StackFrame } from './protocol.js';
|
|
3
3
|
|
|
4
4
|
/** Aceita tudo que o handler pode ter em mãos: bytes ou um caminho de arquivo (só Node). */
|
|
5
5
|
type AttachInput = Uint8Array | ArrayBuffer | string;
|
|
@@ -50,6 +50,8 @@ interface Logger {
|
|
|
50
50
|
declare class Client {
|
|
51
51
|
#private;
|
|
52
52
|
readonly logger: Logger;
|
|
53
|
+
/** Eco de lifecycle (Transaction.emit) e afins só imprimem no terminal com debug ligado. */
|
|
54
|
+
readonly debug: boolean;
|
|
53
55
|
private readonly transport;
|
|
54
56
|
constructor(options: InitOptions, config?: ResolvedConfig);
|
|
55
57
|
get options(): InitOptions;
|
|
@@ -110,9 +112,10 @@ declare class Transaction {
|
|
|
110
112
|
/** Correlaciona a uma execução de task — preenchido automaticamente em ctx.tx */
|
|
111
113
|
runId?: string | undefined);
|
|
112
114
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
115
|
+
* Registra o início/fim de uma step ou transaction: espelha a linha no sink de
|
|
116
|
+
* lifecycle, se conectado (é assim que os eventos de performance aparecem nos
|
|
117
|
+
* logs da run no app), e ecoa no terminal apenas com `debug: true` — o SDK não
|
|
118
|
+
* escreve no stdout do processo do usuário sem permissão.
|
|
116
119
|
*/
|
|
117
120
|
emit(kind: 'transaction' | 'step', phase: 'start' | 'finish', label: string, startTs?: string, endTs?: string, status?: StepStatus | string): void;
|
|
118
121
|
/** Conecta o espelho de lifecycle (RunReporter) e faz replay do backlog em ordem. */
|
|
@@ -167,11 +170,41 @@ interface RunLogger {
|
|
|
167
170
|
error(message: string): void;
|
|
168
171
|
success(message: string): void;
|
|
169
172
|
}
|
|
173
|
+
interface RunItemOptions {
|
|
174
|
+
/**
|
|
175
|
+
* Identidade de negócio do item na run (ex.: nº da nota fiscal). Repetido na
|
|
176
|
+
* mesma run = ignorado pelo servidor (registro único, retry idempotente).
|
|
177
|
+
* Omitido: o SDK gera um id único e cada chamada vira um item novo.
|
|
178
|
+
*/
|
|
179
|
+
id?: string;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Registro dos itens que a execução processou/gerou (a entrega do bot: notas
|
|
183
|
+
* fiscais baixadas, boletos emitidos...). Chamadas são síncronas — os itens são
|
|
184
|
+
* bufferizados e enviados em lotes; o agente drena o buffer antes do reporte final.
|
|
185
|
+
*/
|
|
186
|
+
interface RunItems {
|
|
187
|
+
/** Forma genérica — os atalhos succeeded/failed/occurrence cobrem o dia a dia. */
|
|
188
|
+
add(item: {
|
|
189
|
+
status: RunItemStatus;
|
|
190
|
+
id?: string;
|
|
191
|
+
message?: string;
|
|
192
|
+
payload?: Record<string, unknown>;
|
|
193
|
+
}): void;
|
|
194
|
+
/** Item processado com sucesso. `payload` = atributos do item (viram colunas na plataforma). */
|
|
195
|
+
succeeded(payload?: Record<string, unknown>, options?: RunItemOptions): void;
|
|
196
|
+
/** Falha técnica ao processar o item — `message` é o motivo exibido na plataforma. */
|
|
197
|
+
failed(message: string, payload?: Record<string, unknown>, options?: RunItemOptions): void;
|
|
198
|
+
/** Ocorrência de negócio (ex.: "nota nº 10 não encontrada") — não é falha do bot. */
|
|
199
|
+
occurrence(message: string, payload?: Record<string, unknown>, options?: RunItemOptions): void;
|
|
200
|
+
}
|
|
170
201
|
interface RunContext {
|
|
171
202
|
runId: string;
|
|
172
203
|
task: string;
|
|
173
204
|
/** Log bufferizado — enviado ao servidor em PATCHes incrementais com throttle */
|
|
174
205
|
log: RunLogger;
|
|
206
|
+
/** Itens processados/gerados pela execução — a entrega do bot, com status e atributos */
|
|
207
|
+
items: RunItems;
|
|
175
208
|
/**
|
|
176
209
|
* Transaction de performance desta execução — já correlacionada à run e finalizada
|
|
177
210
|
* automaticamente ao fim do handler. Use `ctx.tx.startChild(op, description)` pra medir
|
|
@@ -303,4 +336,4 @@ declare function close(timeoutMs?: number): Promise<boolean>;
|
|
|
303
336
|
|
|
304
337
|
declare function scrubSecrets(text: string): string;
|
|
305
338
|
|
|
306
|
-
export { type AttachInput, Breadcrumb, type BreadcrumbInput, type CaptureAttachment, type CaptureHint, Client, DEFAULT_BASE_URL, EventPayload, EventUser, type HeartbeatOptions, HeartbeatPayload, type InitOptions, RunAttachmentContentType, type RunContext, RunLogLevel, Scope, Severity, Step, StepData, type TaskHandler, type TasksOptions, Transaction, TransactionPayload, addBreadcrumb, captureException, captureMessage, close, flush, getClient, init, register, scrubSecrets, setContext, setExtra, setTag, setTags, setUser, start, startTransaction, stop, withScope };
|
|
339
|
+
export { type AttachInput, Breadcrumb, type BreadcrumbInput, type CaptureAttachment, type CaptureHint, Client, DEFAULT_BASE_URL, EventPayload, EventUser, type HeartbeatOptions, HeartbeatPayload, type InitOptions, RunAttachmentContentType, type RunContext, type RunItemOptions, RunItemStatus, type RunItems, RunLogLevel, Scope, Severity, Step, StepData, type TaskHandler, type TasksOptions, Transaction, TransactionPayload, addBreadcrumb, captureException, captureMessage, close, flush, getClient, init, register, scrubSecrets, setContext, setExtra, setTag, setTags, setUser, start, startTransaction, stop, withScope };
|
package/dist/index.js
CHANGED
|
@@ -103,7 +103,7 @@ function scrubSecrets(text) {
|
|
|
103
103
|
|
|
104
104
|
// src/utils.ts
|
|
105
105
|
var SDK_NAME = "hobots";
|
|
106
|
-
var SDK_VERSION = true ? "
|
|
106
|
+
var SDK_VERSION = true ? "2.0.0" : "0.0.0-dev";
|
|
107
107
|
function isNode() {
|
|
108
108
|
return typeof process !== "undefined" && typeof process.versions?.node === "string";
|
|
109
109
|
}
|
|
@@ -296,7 +296,8 @@ var Transport = class {
|
|
|
296
296
|
return;
|
|
297
297
|
}
|
|
298
298
|
if (this.buffer.length >= this.maxQueueSize) {
|
|
299
|
-
this.buffer.
|
|
299
|
+
const dropIndex = this.buffer.findIndex((queued) => queued.type !== "heartbeat");
|
|
300
|
+
this.buffer.splice(dropIndex === -1 ? 0 : dropIndex, 1);
|
|
300
301
|
this.logger.warn("fila cheia, item mais antigo descartado");
|
|
301
302
|
}
|
|
302
303
|
this.buffer.push(item);
|
|
@@ -387,6 +388,8 @@ function runtimeContexts() {
|
|
|
387
388
|
}
|
|
388
389
|
var Client = class {
|
|
389
390
|
logger;
|
|
391
|
+
/** Eco de lifecycle (Transaction.emit) e afins só imprimem no terminal com debug ligado. */
|
|
392
|
+
debug;
|
|
390
393
|
// campos privados + getters de protótipo: o clientSecret não aparece em
|
|
391
394
|
// JSON.stringify/serialização enumerável do Client (ex.: um objeto capturado
|
|
392
395
|
// que referencie o client), mas client.options/config seguem funcionando
|
|
@@ -395,7 +398,8 @@ var Client = class {
|
|
|
395
398
|
transport;
|
|
396
399
|
constructor(options, config = resolveConfig(options)) {
|
|
397
400
|
this.#options = options;
|
|
398
|
-
this.
|
|
401
|
+
this.debug = !!options.debug;
|
|
402
|
+
this.logger = makeLogger(this.debug);
|
|
399
403
|
this.#config = config;
|
|
400
404
|
this.transport = new Transport({
|
|
401
405
|
endpoint: this.#config.ingestEndpoint,
|
|
@@ -482,6 +486,8 @@ var Client = class {
|
|
|
482
486
|
|
|
483
487
|
// src/heartbeat.ts
|
|
484
488
|
var DEFAULT_INTERVAL_MS = 3e4;
|
|
489
|
+
var MIN_REPORTED_INTERVAL_MS = 1e3;
|
|
490
|
+
var MAX_REPORTED_INTERVAL_MS = 9e5;
|
|
485
491
|
var Heartbeat = class {
|
|
486
492
|
constructor(client2, options = {}, instanceId) {
|
|
487
493
|
this.client = client2;
|
|
@@ -498,12 +504,22 @@ var Heartbeat = class {
|
|
|
498
504
|
start() {
|
|
499
505
|
if (this.timer) return;
|
|
500
506
|
this.beat();
|
|
501
|
-
this.
|
|
507
|
+
this.schedule();
|
|
508
|
+
}
|
|
509
|
+
/** Jitter de ±10%: uma frota iniciada junto não sincroniza todos os beats no mesmo segundo. */
|
|
510
|
+
schedule() {
|
|
511
|
+
this.timer = setTimeout(
|
|
512
|
+
() => {
|
|
513
|
+
this.beat();
|
|
514
|
+
this.schedule();
|
|
515
|
+
},
|
|
516
|
+
this.intervalMs * (0.9 + Math.random() * 0.2)
|
|
517
|
+
);
|
|
502
518
|
unrefTimer(this.timer);
|
|
503
519
|
}
|
|
504
520
|
stop() {
|
|
505
521
|
if (this.timer) {
|
|
506
|
-
|
|
522
|
+
clearTimeout(this.timer);
|
|
507
523
|
this.timer = void 0;
|
|
508
524
|
}
|
|
509
525
|
}
|
|
@@ -511,7 +527,7 @@ var Heartbeat = class {
|
|
|
511
527
|
this.client.sendHeartbeat({
|
|
512
528
|
instance_id: this.instanceId,
|
|
513
529
|
name: this.name,
|
|
514
|
-
interval_ms: this.intervalMs,
|
|
530
|
+
interval_ms: Math.min(Math.max(this.intervalMs, MIN_REPORTED_INTERVAL_MS), MAX_REPORTED_INTERVAL_MS),
|
|
515
531
|
timestamp: nowISO(),
|
|
516
532
|
metadata: this.metadata
|
|
517
533
|
});
|
|
@@ -758,15 +774,18 @@ var Transaction = class {
|
|
|
758
774
|
// antes do RunReporter conectar) — replay no setLifecycleSink
|
|
759
775
|
lifecycleBacklog = [];
|
|
760
776
|
/**
|
|
761
|
-
*
|
|
762
|
-
*
|
|
763
|
-
*
|
|
777
|
+
* Registra o início/fim de uma step ou transaction: espelha a linha no sink de
|
|
778
|
+
* lifecycle, se conectado (é assim que os eventos de performance aparecem nos
|
|
779
|
+
* logs da run no app), e ecoa no terminal apenas com `debug: true` — o SDK não
|
|
780
|
+
* escreve no stdout do processo do usuário sem permissão.
|
|
764
781
|
*/
|
|
765
782
|
emit(kind, phase, label, startTs, endTs, status) {
|
|
766
783
|
const durationMs = startTs && endTs ? new Date(endTs).getTime() - new Date(startTs).getTime() : void 0;
|
|
767
|
-
const tag = colorize(`[hobots:${kind}] ${phase.toUpperCase()}`, phase === "finish" ? colorForStatus(status ?? "ok") : "cyan");
|
|
768
784
|
const suffix = durationMs != null ? ` (${durationMs}ms)` : "";
|
|
769
|
-
|
|
785
|
+
if (this.client?.debug) {
|
|
786
|
+
const tag = colorize(`[hobots:${kind}] ${phase.toUpperCase()}`, phase === "finish" ? colorForStatus(status ?? "ok") : "cyan");
|
|
787
|
+
console.log(`${tag} ${label}${suffix}`);
|
|
788
|
+
}
|
|
770
789
|
const level = phase === "finish" ? levelForStatus(status ?? "ok") : "info";
|
|
771
790
|
const message = `[${kind}] ${phase.toUpperCase()} ${label}${suffix}`;
|
|
772
791
|
if (this.lifecycleSink) this.lifecycleSink(level, message);
|
|
@@ -929,6 +948,38 @@ var TasksApi = class {
|
|
|
929
948
|
signal: AbortSignal.timeout(timeoutMs)
|
|
930
949
|
});
|
|
931
950
|
}
|
|
951
|
+
/**
|
|
952
|
+
* Envia um lote de itens de execução. Retry em falha de rede/5xx/429-transporte;
|
|
953
|
+
* 4xx é definitivo (404 = servidor sem o recurso, 429 = cap de itens por run) —
|
|
954
|
+
* o RunReporter trata por status.
|
|
955
|
+
*/
|
|
956
|
+
async createItems(runId, request) {
|
|
957
|
+
let response;
|
|
958
|
+
try {
|
|
959
|
+
response = await fetchWithRetry(
|
|
960
|
+
() => fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}/items`, {
|
|
961
|
+
method: "POST",
|
|
962
|
+
headers: this.headers(),
|
|
963
|
+
body: JSON.stringify(request),
|
|
964
|
+
signal: AbortSignal.timeout(UPDATE_TIMEOUT_MS)
|
|
965
|
+
}),
|
|
966
|
+
{ logger: this.logger, label: `itens da run ${runId}` }
|
|
967
|
+
);
|
|
968
|
+
} catch (error) {
|
|
969
|
+
if (error instanceof Error && error.message === "HTTP 429") {
|
|
970
|
+
throw new TasksApiError("envio de itens rejeitado (HTTP 429)", 429);
|
|
971
|
+
}
|
|
972
|
+
throw new Error(`envio de itens da run ${runId} descartado ap\xF3s retries: ${String(error)}`);
|
|
973
|
+
}
|
|
974
|
+
if (!response.ok) {
|
|
975
|
+
throw new TasksApiError(`envio de itens rejeitado (HTTP ${response.status})`, response.status);
|
|
976
|
+
}
|
|
977
|
+
const body = await response.json().catch(() => null);
|
|
978
|
+
return {
|
|
979
|
+
accepted: typeof body?.accepted === "number" ? body.accepted : request.items.length,
|
|
980
|
+
duplicates: typeof body?.duplicates === "number" ? body.duplicates : 0
|
|
981
|
+
};
|
|
982
|
+
}
|
|
932
983
|
/**
|
|
933
984
|
* Registra o anexo e obtém a URL presignada de upload. 404/501/503 significam
|
|
934
985
|
* "esta instalação não tem anexos" — o RunReporter degrada em silêncio a partir daí.
|
|
@@ -1086,6 +1137,22 @@ var MAX_CANCEL_CHECK_INTERVAL_MS = 15e3;
|
|
|
1086
1137
|
var MAX_LOG_MESSAGE_CHARS = 1e4;
|
|
1087
1138
|
var MAX_LOGS_PER_PATCH = 100;
|
|
1088
1139
|
var FLUSH_AT_BUFFERED = 20;
|
|
1140
|
+
var MAX_ITEMS_PER_RUN = 1e4;
|
|
1141
|
+
var MAX_ITEMS_PER_REQUEST = 500;
|
|
1142
|
+
var MAX_ITEM_MESSAGE_CHARS = 2e3;
|
|
1143
|
+
var MAX_ITEM_PAYLOAD_BYTES = 16384;
|
|
1144
|
+
var ITEMS_FLUSH_AT_BUFFERED = 100;
|
|
1145
|
+
var ITEMS_DRAIN_MS = 15e3;
|
|
1146
|
+
function scrubPayloadValue(value, depth) {
|
|
1147
|
+
if (typeof value === "string") return scrubSecrets(value);
|
|
1148
|
+
if (depth >= 4 || value === null || typeof value !== "object") return value;
|
|
1149
|
+
if (Array.isArray(value)) return value.map((entry) => scrubPayloadValue(entry, depth + 1));
|
|
1150
|
+
const out = {};
|
|
1151
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1152
|
+
out[key] = scrubPayloadValue(entry, depth + 1);
|
|
1153
|
+
}
|
|
1154
|
+
return out;
|
|
1155
|
+
}
|
|
1089
1156
|
var RunReporter = class {
|
|
1090
1157
|
constructor(api, runId, task, logger, tx, flushIntervalMs, cancelCheckIntervalMs) {
|
|
1091
1158
|
this.api = api;
|
|
@@ -1104,11 +1171,18 @@ var RunReporter = class {
|
|
|
1104
1171
|
success: (message) => this.log("success", message)
|
|
1105
1172
|
};
|
|
1106
1173
|
tx.setLifecycleSink((level, message) => this.push(level, message));
|
|
1174
|
+
const items = {
|
|
1175
|
+
add: (item) => this.addItem(item.status, item.message, item.payload, item.id),
|
|
1176
|
+
succeeded: (payload, options) => this.addItem("succeeded", void 0, payload, options?.id),
|
|
1177
|
+
failed: (message, payload, options) => this.addItem("failed", message, payload, options?.id),
|
|
1178
|
+
occurrence: (message, payload, options) => this.addItem("occurrence", message, payload, options?.id)
|
|
1179
|
+
};
|
|
1107
1180
|
const signal = this.abortController.signal;
|
|
1108
1181
|
this.context = {
|
|
1109
1182
|
runId,
|
|
1110
1183
|
task,
|
|
1111
1184
|
log,
|
|
1185
|
+
items,
|
|
1112
1186
|
tx,
|
|
1113
1187
|
signal,
|
|
1114
1188
|
throwIfCanceled: () => {
|
|
@@ -1127,6 +1201,23 @@ var RunReporter = class {
|
|
|
1127
1201
|
abortController = new AbortController();
|
|
1128
1202
|
flushIntervalMs;
|
|
1129
1203
|
cancelCheckIntervalMs;
|
|
1204
|
+
// Itens de execução vivem FORA de `this.chain` (status/liveness não pode ficar
|
|
1205
|
+
// atrás de milhares de itens), mas têm cadeia própria serializada — ordem de
|
|
1206
|
+
// envio preservada e sem lotes sobrepostos.
|
|
1207
|
+
itemsBuffer = [];
|
|
1208
|
+
itemsChain = Promise.resolve();
|
|
1209
|
+
itemsTimer;
|
|
1210
|
+
itemSeq = 0;
|
|
1211
|
+
itemCount = 0;
|
|
1212
|
+
/** Servidor sem o recurso (404) — para de tentar (e de avisar) nesta run. */
|
|
1213
|
+
itemsUnsupported = false;
|
|
1214
|
+
/** Cap de itens por run atingido (client-side ou 429) — degrada com um aviso só. */
|
|
1215
|
+
itemsQuotaExceeded = false;
|
|
1216
|
+
/**
|
|
1217
|
+
* Só o kill() do exit path fecha os itens — `dead` (run cancelada/finalizada no
|
|
1218
|
+
* servidor) NÃO fecha: o servidor aceita itens de run terminal (trabalho já feito).
|
|
1219
|
+
*/
|
|
1220
|
+
itemsClosed = false;
|
|
1130
1221
|
// Uploads de anexo vivem FORA de `this.chain`: a cadeia é um FIFO puro de
|
|
1131
1222
|
// status/logs e não pode ficar bloqueada esperando um binário subir.
|
|
1132
1223
|
pendingAttachments = /* @__PURE__ */ new Set();
|
|
@@ -1153,6 +1244,12 @@ var RunReporter = class {
|
|
|
1153
1244
|
this.dead = true;
|
|
1154
1245
|
this.clearFlushTimer();
|
|
1155
1246
|
this.buffer = [];
|
|
1247
|
+
this.itemsClosed = true;
|
|
1248
|
+
this.clearItemsTimer();
|
|
1249
|
+
if (this.itemsBuffer.length > 0) {
|
|
1250
|
+
this.logger.warn(`${this.itemsBuffer.length} item(ns) de execu\xE7\xE3o descartados (processo encerrando)`);
|
|
1251
|
+
this.itemsBuffer = [];
|
|
1252
|
+
}
|
|
1156
1253
|
const waiting = this.uploadQueue.splice(0, this.uploadQueue.length);
|
|
1157
1254
|
for (const resume of waiting) resume();
|
|
1158
1255
|
}
|
|
@@ -1255,6 +1352,120 @@ var RunReporter = class {
|
|
|
1255
1352
|
finalize() {
|
|
1256
1353
|
return this.chain;
|
|
1257
1354
|
}
|
|
1355
|
+
// ── Itens de execução ────────────────────────────────────────────────────────
|
|
1356
|
+
/**
|
|
1357
|
+
* Bufferiza um item (síncrono — a chamada nunca espera rede). Diferente do PATCH
|
|
1358
|
+
* de status, o servidor aceita itens mesmo com a run cancelada (trabalho já feito),
|
|
1359
|
+
* então um reporter morto não descarta: só um kill() de exit path descarta.
|
|
1360
|
+
*/
|
|
1361
|
+
addItem(status, message, payload, id) {
|
|
1362
|
+
if (this.itemsClosed || this.itemsUnsupported) return;
|
|
1363
|
+
if (this.itemCount >= MAX_ITEMS_PER_RUN) {
|
|
1364
|
+
if (!this.itemsQuotaExceeded) {
|
|
1365
|
+
this.itemsQuotaExceeded = true;
|
|
1366
|
+
this.log("warning", `itens descartados: limite de ${MAX_ITEMS_PER_RUN} por execu\xE7\xE3o atingido`);
|
|
1367
|
+
}
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
this.itemCount++;
|
|
1371
|
+
let scrubbedPayload;
|
|
1372
|
+
if (payload) {
|
|
1373
|
+
scrubbedPayload = scrubPayloadValue(payload, 0);
|
|
1374
|
+
let serializedLength;
|
|
1375
|
+
try {
|
|
1376
|
+
serializedLength = JSON.stringify(scrubbedPayload).length;
|
|
1377
|
+
} catch {
|
|
1378
|
+
}
|
|
1379
|
+
if (serializedLength === void 0 || serializedLength > MAX_ITEM_PAYLOAD_BYTES) {
|
|
1380
|
+
this.logger.warn(
|
|
1381
|
+
`payload do item descartado (${serializedLength === void 0 ? "n\xE3o serializ\xE1vel" : `${serializedLength} bytes > ${MAX_ITEM_PAYLOAD_BYTES}`}) \u2014 o item foi mantido`
|
|
1382
|
+
);
|
|
1383
|
+
scrubbedPayload = void 0;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
this.itemsBuffer.push({
|
|
1387
|
+
id: id ? truncate(String(id), 255) : uuid4(),
|
|
1388
|
+
seq: this.itemSeq++,
|
|
1389
|
+
status,
|
|
1390
|
+
...message ? { message: truncate(scrubSecrets(message), MAX_ITEM_MESSAGE_CHARS) } : {},
|
|
1391
|
+
...scrubbedPayload ? { payload: scrubbedPayload } : {},
|
|
1392
|
+
ts: nowISO()
|
|
1393
|
+
});
|
|
1394
|
+
if (this.itemsBuffer.length >= ITEMS_FLUSH_AT_BUFFERED) {
|
|
1395
|
+
this.flushItems();
|
|
1396
|
+
} else {
|
|
1397
|
+
this.scheduleItemsFlush();
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
scheduleItemsFlush() {
|
|
1401
|
+
if (this.itemsTimer) return;
|
|
1402
|
+
this.itemsTimer = setTimeout(() => {
|
|
1403
|
+
this.itemsTimer = void 0;
|
|
1404
|
+
this.flushItems();
|
|
1405
|
+
}, this.flushIntervalMs);
|
|
1406
|
+
unrefTimer(this.itemsTimer);
|
|
1407
|
+
}
|
|
1408
|
+
clearItemsTimer() {
|
|
1409
|
+
if (this.itemsTimer) {
|
|
1410
|
+
clearTimeout(this.itemsTimer);
|
|
1411
|
+
this.itemsTimer = void 0;
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
/** Envia o buffer de itens em lotes serializados na cadeia própria. */
|
|
1415
|
+
flushItems() {
|
|
1416
|
+
this.clearItemsTimer();
|
|
1417
|
+
if (this.itemsBuffer.length === 0 || this.itemsUnsupported) return;
|
|
1418
|
+
const items = this.itemsBuffer.splice(0, this.itemsBuffer.length);
|
|
1419
|
+
for (let i = 0; i < items.length; i += MAX_ITEMS_PER_REQUEST) {
|
|
1420
|
+
const batch = items.slice(i, i + MAX_ITEMS_PER_REQUEST);
|
|
1421
|
+
this.itemsChain = this.itemsChain.then(() => this.sendItems(batch));
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
async sendItems(batch) {
|
|
1425
|
+
if (this.itemsUnsupported) return;
|
|
1426
|
+
try {
|
|
1427
|
+
await this.api.createItems(this.runId, { items: batch });
|
|
1428
|
+
} catch (error) {
|
|
1429
|
+
if (error instanceof TasksApiError && error.status === 404) {
|
|
1430
|
+
this.itemsUnsupported = true;
|
|
1431
|
+
this.logger.warn("itens de execu\xE7\xE3o n\xE3o s\xE3o suportados por este servidor Hobots \u2014 descartados");
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
if (error instanceof TasksApiError && error.status === 429) {
|
|
1435
|
+
if (!this.itemsQuotaExceeded) {
|
|
1436
|
+
this.itemsQuotaExceeded = true;
|
|
1437
|
+
this.log("warning", "itens descartados: limite de itens por execu\xE7\xE3o atingido no servidor");
|
|
1438
|
+
}
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
if (error instanceof TasksApiError) {
|
|
1442
|
+
this.logger.error(`lote de ${batch.length} item(ns) rejeitado pelo servidor:`, error);
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
this.itemsBuffer.unshift(...batch);
|
|
1446
|
+
this.scheduleItemsFlush();
|
|
1447
|
+
this.logger.error(`lote de ${batch.length} item(ns) n\xE3o enviado (devolvido ao buffer):`, error);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Dreno final de itens antes do PATCH terminal (com teto): garante que os
|
|
1452
|
+
* totalizadores estejam completos quando a run vira succeeded/failed. Como o
|
|
1453
|
+
* servidor aceita itens tardios, perder a corrida aqui não perde dados.
|
|
1454
|
+
*/
|
|
1455
|
+
async finalizeItems(timeoutMs = ITEMS_DRAIN_MS) {
|
|
1456
|
+
this.flushItems();
|
|
1457
|
+
await Promise.race([
|
|
1458
|
+
this.itemsChain,
|
|
1459
|
+
new Promise((resolve) => unrefTimer(setTimeout(resolve, timeoutMs)))
|
|
1460
|
+
]);
|
|
1461
|
+
if (this.itemsBuffer.length > 0 && !this.itemsUnsupported && !this.itemsQuotaExceeded) {
|
|
1462
|
+
this.flushItems();
|
|
1463
|
+
await Promise.race([
|
|
1464
|
+
this.itemsChain,
|
|
1465
|
+
new Promise((resolve) => unrefTimer(setTimeout(resolve, timeoutMs)))
|
|
1466
|
+
]);
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1258
1469
|
// ── Anexos ───────────────────────────────────────────────────────────────────
|
|
1259
1470
|
/**
|
|
1260
1471
|
* Registra a linha de log com o id do anexo (síncrono, na ordem certa) e dispara
|
|
@@ -1621,13 +1832,17 @@ var TaskAgent = class {
|
|
|
1621
1832
|
try {
|
|
1622
1833
|
const response = await this.api.poll(
|
|
1623
1834
|
{
|
|
1624
|
-
|
|
1835
|
+
instance: {
|
|
1625
1836
|
// identidade canônica do agente no painel — o mesmo instanceId do
|
|
1626
1837
|
// heartbeat da Telemetria, então os dois canais convergem em um agente
|
|
1627
1838
|
instance_id: this.instanceId,
|
|
1628
1839
|
version: SDK_VERSION,
|
|
1629
1840
|
host: hostname(),
|
|
1630
|
-
tasks: [...this.registry.keys()]
|
|
1841
|
+
tasks: [...this.registry.keys()],
|
|
1842
|
+
// o servidor calcula a tolerância de offline (3×) a partir disto em
|
|
1843
|
+
// agentes poll-only — sem o campo, um poll lento flaparia offline/online.
|
|
1844
|
+
// Clampado à faixa aceita pelo schema do poll (1s..15min).
|
|
1845
|
+
poll_interval_ms: Math.min(Math.max(this.pollIntervalMs, 1e3), 9e5)
|
|
1631
1846
|
},
|
|
1632
1847
|
max: Math.min(MAX_CONCURRENCY, this.concurrency - this.activeRuns.size),
|
|
1633
1848
|
tasks: [...this.registry.keys()]
|
|
@@ -1699,12 +1914,14 @@ var TaskAgent = class {
|
|
|
1699
1914
|
() => this.invokeWithTimeout(handler, run, reporter)
|
|
1700
1915
|
);
|
|
1701
1916
|
await reporter.finalizeAttachments();
|
|
1917
|
+
await reporter.finalizeItems();
|
|
1702
1918
|
tx.setStatus("ok");
|
|
1703
1919
|
tx.finish();
|
|
1704
1920
|
reporter.reportSuccess();
|
|
1705
1921
|
this.logger.debug(`run ${run.id} conclu\xEDda`);
|
|
1706
1922
|
} catch (error) {
|
|
1707
1923
|
await reporter.finalizeAttachments();
|
|
1924
|
+
await reporter.finalizeItems();
|
|
1708
1925
|
tx.setStatus("error");
|
|
1709
1926
|
tx.finish();
|
|
1710
1927
|
reporter.reportFailure(toRunError(error));
|