hobots 1.9.2 → 1.10.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 +195 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -3
- package/dist/index.d.ts +33 -3
- package/dist/index.js +195 -1
- package/dist/index.js.map +1 -1
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.cts +34 -1
- package/dist/protocol.d.ts +34 -1
- package/docs/api-reference.md +11 -0
- package/docs/protocol.md +35 -0
- 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 { AgentInfo, Envelope, EnvelopeItem, ExceptionData, PollRequest, PollResponse, RunAttachmentConfirmRequest, RunAttachmentConfirmResponse, RunAttachmentCreateRequest, RunAttachmentCreateResponse, RunDescriptor, RunError, RunLogEntry, RunStatus, RunUpdateRequest, RunUpdateResponse, RunUpdateStatus, StackFrame } from './protocol.cjs';
|
|
1
|
+
import { Breadcrumb, EventUser, Severity, EventPayload, TransactionPayload, HeartbeatPayload, StepStatus, RunLogLevel, StepData, RunItemStatus, RunAttachmentContentType } from './protocol.cjs';
|
|
2
|
+
export { AgentInfo, Envelope, EnvelopeItem, ExceptionData, 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;
|
|
@@ -167,11 +167,41 @@ interface RunLogger {
|
|
|
167
167
|
error(message: string): void;
|
|
168
168
|
success(message: string): void;
|
|
169
169
|
}
|
|
170
|
+
interface RunItemOptions {
|
|
171
|
+
/**
|
|
172
|
+
* Identidade de negócio do item na run (ex.: nº da nota fiscal). Repetido na
|
|
173
|
+
* mesma run = ignorado pelo servidor (registro único, retry idempotente).
|
|
174
|
+
* Omitido: o SDK gera um id único e cada chamada vira um item novo.
|
|
175
|
+
*/
|
|
176
|
+
id?: string;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Registro dos itens que a execução processou/gerou (a entrega do bot: notas
|
|
180
|
+
* fiscais baixadas, boletos emitidos...). Chamadas são síncronas — os itens são
|
|
181
|
+
* bufferizados e enviados em lotes; o agente drena o buffer antes do reporte final.
|
|
182
|
+
*/
|
|
183
|
+
interface RunItems {
|
|
184
|
+
/** Forma genérica — os atalhos succeeded/failed/occurrence cobrem o dia a dia. */
|
|
185
|
+
add(item: {
|
|
186
|
+
status: RunItemStatus;
|
|
187
|
+
id?: string;
|
|
188
|
+
message?: string;
|
|
189
|
+
payload?: Record<string, unknown>;
|
|
190
|
+
}): void;
|
|
191
|
+
/** Item processado com sucesso. `payload` = atributos do item (viram colunas na plataforma). */
|
|
192
|
+
succeeded(payload?: Record<string, unknown>, options?: RunItemOptions): void;
|
|
193
|
+
/** Falha técnica ao processar o item — `message` é o motivo exibido na plataforma. */
|
|
194
|
+
failed(message: string, payload?: Record<string, unknown>, options?: RunItemOptions): void;
|
|
195
|
+
/** Ocorrência de negócio (ex.: "nota nº 10 não encontrada") — não é falha do bot. */
|
|
196
|
+
occurrence(message: string, payload?: Record<string, unknown>, options?: RunItemOptions): void;
|
|
197
|
+
}
|
|
170
198
|
interface RunContext {
|
|
171
199
|
runId: string;
|
|
172
200
|
task: string;
|
|
173
201
|
/** Log bufferizado — enviado ao servidor em PATCHes incrementais com throttle */
|
|
174
202
|
log: RunLogger;
|
|
203
|
+
/** Itens processados/gerados pela execução — a entrega do bot, com status e atributos */
|
|
204
|
+
items: RunItems;
|
|
175
205
|
/**
|
|
176
206
|
* Transaction de performance desta execução — já correlacionada à run e finalizada
|
|
177
207
|
* automaticamente ao fim do handler. Use `ctx.tx.startChild(op, description)` pra medir
|
|
@@ -303,4 +333,4 @@ declare function close(timeoutMs?: number): Promise<boolean>;
|
|
|
303
333
|
|
|
304
334
|
declare function scrubSecrets(text: string): string;
|
|
305
335
|
|
|
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 };
|
|
336
|
+
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 { AgentInfo, Envelope, EnvelopeItem, ExceptionData, PollRequest, PollResponse, RunAttachmentConfirmRequest, RunAttachmentConfirmResponse, RunAttachmentCreateRequest, RunAttachmentCreateResponse, RunDescriptor, RunError, RunLogEntry, RunStatus, RunUpdateRequest, RunUpdateResponse, RunUpdateStatus, StackFrame } from './protocol.js';
|
|
1
|
+
import { Breadcrumb, EventUser, Severity, EventPayload, TransactionPayload, HeartbeatPayload, StepStatus, RunLogLevel, StepData, RunItemStatus, RunAttachmentContentType } from './protocol.js';
|
|
2
|
+
export { AgentInfo, Envelope, EnvelopeItem, ExceptionData, 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;
|
|
@@ -167,11 +167,41 @@ interface RunLogger {
|
|
|
167
167
|
error(message: string): void;
|
|
168
168
|
success(message: string): void;
|
|
169
169
|
}
|
|
170
|
+
interface RunItemOptions {
|
|
171
|
+
/**
|
|
172
|
+
* Identidade de negócio do item na run (ex.: nº da nota fiscal). Repetido na
|
|
173
|
+
* mesma run = ignorado pelo servidor (registro único, retry idempotente).
|
|
174
|
+
* Omitido: o SDK gera um id único e cada chamada vira um item novo.
|
|
175
|
+
*/
|
|
176
|
+
id?: string;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Registro dos itens que a execução processou/gerou (a entrega do bot: notas
|
|
180
|
+
* fiscais baixadas, boletos emitidos...). Chamadas são síncronas — os itens são
|
|
181
|
+
* bufferizados e enviados em lotes; o agente drena o buffer antes do reporte final.
|
|
182
|
+
*/
|
|
183
|
+
interface RunItems {
|
|
184
|
+
/** Forma genérica — os atalhos succeeded/failed/occurrence cobrem o dia a dia. */
|
|
185
|
+
add(item: {
|
|
186
|
+
status: RunItemStatus;
|
|
187
|
+
id?: string;
|
|
188
|
+
message?: string;
|
|
189
|
+
payload?: Record<string, unknown>;
|
|
190
|
+
}): void;
|
|
191
|
+
/** Item processado com sucesso. `payload` = atributos do item (viram colunas na plataforma). */
|
|
192
|
+
succeeded(payload?: Record<string, unknown>, options?: RunItemOptions): void;
|
|
193
|
+
/** Falha técnica ao processar o item — `message` é o motivo exibido na plataforma. */
|
|
194
|
+
failed(message: string, payload?: Record<string, unknown>, options?: RunItemOptions): void;
|
|
195
|
+
/** Ocorrência de negócio (ex.: "nota nº 10 não encontrada") — não é falha do bot. */
|
|
196
|
+
occurrence(message: string, payload?: Record<string, unknown>, options?: RunItemOptions): void;
|
|
197
|
+
}
|
|
170
198
|
interface RunContext {
|
|
171
199
|
runId: string;
|
|
172
200
|
task: string;
|
|
173
201
|
/** Log bufferizado — enviado ao servidor em PATCHes incrementais com throttle */
|
|
174
202
|
log: RunLogger;
|
|
203
|
+
/** Itens processados/gerados pela execução — a entrega do bot, com status e atributos */
|
|
204
|
+
items: RunItems;
|
|
175
205
|
/**
|
|
176
206
|
* Transaction de performance desta execução — já correlacionada à run e finalizada
|
|
177
207
|
* automaticamente ao fim do handler. Use `ctx.tx.startChild(op, description)` pra medir
|
|
@@ -303,4 +333,4 @@ declare function close(timeoutMs?: number): Promise<boolean>;
|
|
|
303
333
|
|
|
304
334
|
declare function scrubSecrets(text: string): string;
|
|
305
335
|
|
|
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 };
|
|
336
|
+
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 ? "1.
|
|
106
|
+
var SDK_VERSION = true ? "1.10.0" : "0.0.0-dev";
|
|
107
107
|
function isNode() {
|
|
108
108
|
return typeof process !== "undefined" && typeof process.versions?.node === "string";
|
|
109
109
|
}
|
|
@@ -929,6 +929,38 @@ var TasksApi = class {
|
|
|
929
929
|
signal: AbortSignal.timeout(timeoutMs)
|
|
930
930
|
});
|
|
931
931
|
}
|
|
932
|
+
/**
|
|
933
|
+
* Envia um lote de itens de execução. Retry em falha de rede/5xx/429-transporte;
|
|
934
|
+
* 4xx é definitivo (404 = servidor sem o recurso, 429 = cap de itens por run) —
|
|
935
|
+
* o RunReporter trata por status.
|
|
936
|
+
*/
|
|
937
|
+
async createItems(runId, request) {
|
|
938
|
+
let response;
|
|
939
|
+
try {
|
|
940
|
+
response = await fetchWithRetry(
|
|
941
|
+
() => fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}/items`, {
|
|
942
|
+
method: "POST",
|
|
943
|
+
headers: this.headers(),
|
|
944
|
+
body: JSON.stringify(request),
|
|
945
|
+
signal: AbortSignal.timeout(UPDATE_TIMEOUT_MS)
|
|
946
|
+
}),
|
|
947
|
+
{ logger: this.logger, label: `itens da run ${runId}` }
|
|
948
|
+
);
|
|
949
|
+
} catch (error) {
|
|
950
|
+
if (error instanceof Error && error.message === "HTTP 429") {
|
|
951
|
+
throw new TasksApiError("envio de itens rejeitado (HTTP 429)", 429);
|
|
952
|
+
}
|
|
953
|
+
throw new Error(`envio de itens da run ${runId} descartado ap\xF3s retries: ${String(error)}`);
|
|
954
|
+
}
|
|
955
|
+
if (!response.ok) {
|
|
956
|
+
throw new TasksApiError(`envio de itens rejeitado (HTTP ${response.status})`, response.status);
|
|
957
|
+
}
|
|
958
|
+
const body = await response.json().catch(() => null);
|
|
959
|
+
return {
|
|
960
|
+
accepted: typeof body?.accepted === "number" ? body.accepted : request.items.length,
|
|
961
|
+
duplicates: typeof body?.duplicates === "number" ? body.duplicates : 0
|
|
962
|
+
};
|
|
963
|
+
}
|
|
932
964
|
/**
|
|
933
965
|
* Registra o anexo e obtém a URL presignada de upload. 404/501/503 significam
|
|
934
966
|
* "esta instalação não tem anexos" — o RunReporter degrada em silêncio a partir daí.
|
|
@@ -1086,6 +1118,22 @@ var MAX_CANCEL_CHECK_INTERVAL_MS = 15e3;
|
|
|
1086
1118
|
var MAX_LOG_MESSAGE_CHARS = 1e4;
|
|
1087
1119
|
var MAX_LOGS_PER_PATCH = 100;
|
|
1088
1120
|
var FLUSH_AT_BUFFERED = 20;
|
|
1121
|
+
var MAX_ITEMS_PER_RUN = 1e4;
|
|
1122
|
+
var MAX_ITEMS_PER_REQUEST = 500;
|
|
1123
|
+
var MAX_ITEM_MESSAGE_CHARS = 2e3;
|
|
1124
|
+
var MAX_ITEM_PAYLOAD_BYTES = 16384;
|
|
1125
|
+
var ITEMS_FLUSH_AT_BUFFERED = 100;
|
|
1126
|
+
var ITEMS_DRAIN_MS = 15e3;
|
|
1127
|
+
function scrubPayloadValue(value, depth) {
|
|
1128
|
+
if (typeof value === "string") return scrubSecrets(value);
|
|
1129
|
+
if (depth >= 4 || value === null || typeof value !== "object") return value;
|
|
1130
|
+
if (Array.isArray(value)) return value.map((entry) => scrubPayloadValue(entry, depth + 1));
|
|
1131
|
+
const out = {};
|
|
1132
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1133
|
+
out[key] = scrubPayloadValue(entry, depth + 1);
|
|
1134
|
+
}
|
|
1135
|
+
return out;
|
|
1136
|
+
}
|
|
1089
1137
|
var RunReporter = class {
|
|
1090
1138
|
constructor(api, runId, task, logger, tx, flushIntervalMs, cancelCheckIntervalMs) {
|
|
1091
1139
|
this.api = api;
|
|
@@ -1104,11 +1152,18 @@ var RunReporter = class {
|
|
|
1104
1152
|
success: (message) => this.log("success", message)
|
|
1105
1153
|
};
|
|
1106
1154
|
tx.setLifecycleSink((level, message) => this.push(level, message));
|
|
1155
|
+
const items = {
|
|
1156
|
+
add: (item) => this.addItem(item.status, item.message, item.payload, item.id),
|
|
1157
|
+
succeeded: (payload, options) => this.addItem("succeeded", void 0, payload, options?.id),
|
|
1158
|
+
failed: (message, payload, options) => this.addItem("failed", message, payload, options?.id),
|
|
1159
|
+
occurrence: (message, payload, options) => this.addItem("occurrence", message, payload, options?.id)
|
|
1160
|
+
};
|
|
1107
1161
|
const signal = this.abortController.signal;
|
|
1108
1162
|
this.context = {
|
|
1109
1163
|
runId,
|
|
1110
1164
|
task,
|
|
1111
1165
|
log,
|
|
1166
|
+
items,
|
|
1112
1167
|
tx,
|
|
1113
1168
|
signal,
|
|
1114
1169
|
throwIfCanceled: () => {
|
|
@@ -1127,6 +1182,23 @@ var RunReporter = class {
|
|
|
1127
1182
|
abortController = new AbortController();
|
|
1128
1183
|
flushIntervalMs;
|
|
1129
1184
|
cancelCheckIntervalMs;
|
|
1185
|
+
// Itens de execução vivem FORA de `this.chain` (status/liveness não pode ficar
|
|
1186
|
+
// atrás de milhares de itens), mas têm cadeia própria serializada — ordem de
|
|
1187
|
+
// envio preservada e sem lotes sobrepostos.
|
|
1188
|
+
itemsBuffer = [];
|
|
1189
|
+
itemsChain = Promise.resolve();
|
|
1190
|
+
itemsTimer;
|
|
1191
|
+
itemSeq = 0;
|
|
1192
|
+
itemCount = 0;
|
|
1193
|
+
/** Servidor sem o recurso (404) — para de tentar (e de avisar) nesta run. */
|
|
1194
|
+
itemsUnsupported = false;
|
|
1195
|
+
/** Cap de itens por run atingido (client-side ou 429) — degrada com um aviso só. */
|
|
1196
|
+
itemsQuotaExceeded = false;
|
|
1197
|
+
/**
|
|
1198
|
+
* Só o kill() do exit path fecha os itens — `dead` (run cancelada/finalizada no
|
|
1199
|
+
* servidor) NÃO fecha: o servidor aceita itens de run terminal (trabalho já feito).
|
|
1200
|
+
*/
|
|
1201
|
+
itemsClosed = false;
|
|
1130
1202
|
// Uploads de anexo vivem FORA de `this.chain`: a cadeia é um FIFO puro de
|
|
1131
1203
|
// status/logs e não pode ficar bloqueada esperando um binário subir.
|
|
1132
1204
|
pendingAttachments = /* @__PURE__ */ new Set();
|
|
@@ -1153,6 +1225,12 @@ var RunReporter = class {
|
|
|
1153
1225
|
this.dead = true;
|
|
1154
1226
|
this.clearFlushTimer();
|
|
1155
1227
|
this.buffer = [];
|
|
1228
|
+
this.itemsClosed = true;
|
|
1229
|
+
this.clearItemsTimer();
|
|
1230
|
+
if (this.itemsBuffer.length > 0) {
|
|
1231
|
+
this.logger.warn(`${this.itemsBuffer.length} item(ns) de execu\xE7\xE3o descartados (processo encerrando)`);
|
|
1232
|
+
this.itemsBuffer = [];
|
|
1233
|
+
}
|
|
1156
1234
|
const waiting = this.uploadQueue.splice(0, this.uploadQueue.length);
|
|
1157
1235
|
for (const resume of waiting) resume();
|
|
1158
1236
|
}
|
|
@@ -1255,6 +1333,120 @@ var RunReporter = class {
|
|
|
1255
1333
|
finalize() {
|
|
1256
1334
|
return this.chain;
|
|
1257
1335
|
}
|
|
1336
|
+
// ── Itens de execução ────────────────────────────────────────────────────────
|
|
1337
|
+
/**
|
|
1338
|
+
* Bufferiza um item (síncrono — a chamada nunca espera rede). Diferente do PATCH
|
|
1339
|
+
* de status, o servidor aceita itens mesmo com a run cancelada (trabalho já feito),
|
|
1340
|
+
* então um reporter morto não descarta: só um kill() de exit path descarta.
|
|
1341
|
+
*/
|
|
1342
|
+
addItem(status, message, payload, id) {
|
|
1343
|
+
if (this.itemsClosed || this.itemsUnsupported) return;
|
|
1344
|
+
if (this.itemCount >= MAX_ITEMS_PER_RUN) {
|
|
1345
|
+
if (!this.itemsQuotaExceeded) {
|
|
1346
|
+
this.itemsQuotaExceeded = true;
|
|
1347
|
+
this.log("warning", `itens descartados: limite de ${MAX_ITEMS_PER_RUN} por execu\xE7\xE3o atingido`);
|
|
1348
|
+
}
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
this.itemCount++;
|
|
1352
|
+
let scrubbedPayload;
|
|
1353
|
+
if (payload) {
|
|
1354
|
+
scrubbedPayload = scrubPayloadValue(payload, 0);
|
|
1355
|
+
let serializedLength;
|
|
1356
|
+
try {
|
|
1357
|
+
serializedLength = JSON.stringify(scrubbedPayload).length;
|
|
1358
|
+
} catch {
|
|
1359
|
+
}
|
|
1360
|
+
if (serializedLength === void 0 || serializedLength > MAX_ITEM_PAYLOAD_BYTES) {
|
|
1361
|
+
this.logger.warn(
|
|
1362
|
+
`payload do item descartado (${serializedLength === void 0 ? "n\xE3o serializ\xE1vel" : `${serializedLength} bytes > ${MAX_ITEM_PAYLOAD_BYTES}`}) \u2014 o item foi mantido`
|
|
1363
|
+
);
|
|
1364
|
+
scrubbedPayload = void 0;
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
this.itemsBuffer.push({
|
|
1368
|
+
id: id ? truncate(String(id), 255) : uuid4(),
|
|
1369
|
+
seq: this.itemSeq++,
|
|
1370
|
+
status,
|
|
1371
|
+
...message ? { message: truncate(scrubSecrets(message), MAX_ITEM_MESSAGE_CHARS) } : {},
|
|
1372
|
+
...scrubbedPayload ? { payload: scrubbedPayload } : {},
|
|
1373
|
+
ts: nowISO()
|
|
1374
|
+
});
|
|
1375
|
+
if (this.itemsBuffer.length >= ITEMS_FLUSH_AT_BUFFERED) {
|
|
1376
|
+
this.flushItems();
|
|
1377
|
+
} else {
|
|
1378
|
+
this.scheduleItemsFlush();
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
scheduleItemsFlush() {
|
|
1382
|
+
if (this.itemsTimer) return;
|
|
1383
|
+
this.itemsTimer = setTimeout(() => {
|
|
1384
|
+
this.itemsTimer = void 0;
|
|
1385
|
+
this.flushItems();
|
|
1386
|
+
}, this.flushIntervalMs);
|
|
1387
|
+
unrefTimer(this.itemsTimer);
|
|
1388
|
+
}
|
|
1389
|
+
clearItemsTimer() {
|
|
1390
|
+
if (this.itemsTimer) {
|
|
1391
|
+
clearTimeout(this.itemsTimer);
|
|
1392
|
+
this.itemsTimer = void 0;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
/** Envia o buffer de itens em lotes serializados na cadeia própria. */
|
|
1396
|
+
flushItems() {
|
|
1397
|
+
this.clearItemsTimer();
|
|
1398
|
+
if (this.itemsBuffer.length === 0 || this.itemsUnsupported) return;
|
|
1399
|
+
const items = this.itemsBuffer.splice(0, this.itemsBuffer.length);
|
|
1400
|
+
for (let i = 0; i < items.length; i += MAX_ITEMS_PER_REQUEST) {
|
|
1401
|
+
const batch = items.slice(i, i + MAX_ITEMS_PER_REQUEST);
|
|
1402
|
+
this.itemsChain = this.itemsChain.then(() => this.sendItems(batch));
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
async sendItems(batch) {
|
|
1406
|
+
if (this.itemsUnsupported) return;
|
|
1407
|
+
try {
|
|
1408
|
+
await this.api.createItems(this.runId, { items: batch });
|
|
1409
|
+
} catch (error) {
|
|
1410
|
+
if (error instanceof TasksApiError && error.status === 404) {
|
|
1411
|
+
this.itemsUnsupported = true;
|
|
1412
|
+
this.logger.warn("itens de execu\xE7\xE3o n\xE3o s\xE3o suportados por este servidor Hobots \u2014 descartados");
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
if (error instanceof TasksApiError && error.status === 429) {
|
|
1416
|
+
if (!this.itemsQuotaExceeded) {
|
|
1417
|
+
this.itemsQuotaExceeded = true;
|
|
1418
|
+
this.log("warning", "itens descartados: limite de itens por execu\xE7\xE3o atingido no servidor");
|
|
1419
|
+
}
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
if (error instanceof TasksApiError) {
|
|
1423
|
+
this.logger.error(`lote de ${batch.length} item(ns) rejeitado pelo servidor:`, error);
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
this.itemsBuffer.unshift(...batch);
|
|
1427
|
+
this.scheduleItemsFlush();
|
|
1428
|
+
this.logger.error(`lote de ${batch.length} item(ns) n\xE3o enviado (devolvido ao buffer):`, error);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
/**
|
|
1432
|
+
* Dreno final de itens antes do PATCH terminal (com teto): garante que os
|
|
1433
|
+
* totalizadores estejam completos quando a run vira succeeded/failed. Como o
|
|
1434
|
+
* servidor aceita itens tardios, perder a corrida aqui não perde dados.
|
|
1435
|
+
*/
|
|
1436
|
+
async finalizeItems(timeoutMs = ITEMS_DRAIN_MS) {
|
|
1437
|
+
this.flushItems();
|
|
1438
|
+
await Promise.race([
|
|
1439
|
+
this.itemsChain,
|
|
1440
|
+
new Promise((resolve) => unrefTimer(setTimeout(resolve, timeoutMs)))
|
|
1441
|
+
]);
|
|
1442
|
+
if (this.itemsBuffer.length > 0 && !this.itemsUnsupported && !this.itemsQuotaExceeded) {
|
|
1443
|
+
this.flushItems();
|
|
1444
|
+
await Promise.race([
|
|
1445
|
+
this.itemsChain,
|
|
1446
|
+
new Promise((resolve) => unrefTimer(setTimeout(resolve, timeoutMs)))
|
|
1447
|
+
]);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1258
1450
|
// ── Anexos ───────────────────────────────────────────────────────────────────
|
|
1259
1451
|
/**
|
|
1260
1452
|
* Registra a linha de log com o id do anexo (síncrono, na ordem certa) e dispara
|
|
@@ -1699,12 +1891,14 @@ var TaskAgent = class {
|
|
|
1699
1891
|
() => this.invokeWithTimeout(handler, run, reporter)
|
|
1700
1892
|
);
|
|
1701
1893
|
await reporter.finalizeAttachments();
|
|
1894
|
+
await reporter.finalizeItems();
|
|
1702
1895
|
tx.setStatus("ok");
|
|
1703
1896
|
tx.finish();
|
|
1704
1897
|
reporter.reportSuccess();
|
|
1705
1898
|
this.logger.debug(`run ${run.id} conclu\xEDda`);
|
|
1706
1899
|
} catch (error) {
|
|
1707
1900
|
await reporter.finalizeAttachments();
|
|
1901
|
+
await reporter.finalizeItems();
|
|
1708
1902
|
tx.setStatus("error");
|
|
1709
1903
|
tx.finish();
|
|
1710
1904
|
reporter.reportFailure(toRunError(error));
|