hobots 3.4.0 → 4.1.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 +38 -28
- package/dist/{chunk-5WIIACWC.js → chunk-HKCDMUA3.js} +1 -2
- package/dist/chunk-HKCDMUA3.js.map +1 -0
- package/dist/index.cjs +179 -231
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +47 -25
- package/dist/index.d.ts +47 -25
- package/dist/index.js +180 -231
- package/dist/index.js.map +1 -1
- package/dist/protocol.cjs +0 -1
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.cts +21 -23
- package/dist/protocol.d.ts +21 -23
- package/dist/protocol.js +1 -1
- package/docs/README.md +10 -8
- package/docs/api-reference.md +34 -20
- package/docs/concepts.md +19 -19
- package/docs/getting-started.md +14 -12
- package/docs/protocol.md +24 -25
- package/docs/{tasks.md → runs.md} +51 -23
- package/package.json +2 -2
- package/dist/chunk-5WIIACWC.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
WIRE_LIMITS
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-HKCDMUA3.js";
|
|
4
4
|
|
|
5
5
|
// src/stacktrace.ts
|
|
6
6
|
var MAX_FRAMES = 50;
|
|
@@ -57,7 +57,7 @@ function scrubSecrets(text) {
|
|
|
57
57
|
|
|
58
58
|
// src/utils.ts
|
|
59
59
|
var SDK_NAME = "hobots";
|
|
60
|
-
var SDK_VERSION = true ? "
|
|
60
|
+
var SDK_VERSION = true ? "4.1.0" : "0.0.0-dev";
|
|
61
61
|
function isNode() {
|
|
62
62
|
return typeof process !== "undefined" && typeof process.versions?.node === "string";
|
|
63
63
|
}
|
|
@@ -514,7 +514,7 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
|
|
|
514
514
|
const origin = fromOption ? "clientSecret" : `env ${CLIENT_SECRET_ENV}`;
|
|
515
515
|
if (!key) {
|
|
516
516
|
throw new Error(
|
|
517
|
-
`${prefix} Client Secret ausente \u2014 defina a env ${CLIENT_SECRET_ENV} (ou passe clientSecret nas op\xE7\xF5es
|
|
517
|
+
`${prefix} Client Secret ausente \u2014 defina a env ${CLIENT_SECRET_ENV} (ou passe clientSecret nas op\xE7\xF5es do register/start); copie o Client Secret do agente em Configura\xE7\xF5es \u2192 Agentes`
|
|
518
518
|
);
|
|
519
519
|
}
|
|
520
520
|
if (!SECRET_PATTERN.test(key)) {
|
|
@@ -527,7 +527,7 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
|
|
|
527
527
|
key,
|
|
528
528
|
baseUrl,
|
|
529
529
|
ingestEndpoint: `${baseUrl}/v1/ingest`,
|
|
530
|
-
|
|
530
|
+
runsBaseUrl: `${baseUrl}/v1/runs`
|
|
531
531
|
};
|
|
532
532
|
}
|
|
533
533
|
|
|
@@ -891,7 +891,7 @@ var TasksApi = class {
|
|
|
891
891
|
const { signal: timeoutSignal, cleanup } = withTimeoutSignal(UPDATE_TIMEOUT_MS, signal);
|
|
892
892
|
let response;
|
|
893
893
|
try {
|
|
894
|
-
response = await fetch(`${this.baseUrl}
|
|
894
|
+
response = await fetch(`${this.baseUrl}`, {
|
|
895
895
|
redirect: "error",
|
|
896
896
|
method: "POST",
|
|
897
897
|
headers: this.headers(),
|
|
@@ -905,12 +905,11 @@ var TasksApi = class {
|
|
|
905
905
|
throw new TasksApiError(`cria\xE7\xE3o da run falhou (HTTP ${response.status})`, response.status);
|
|
906
906
|
}
|
|
907
907
|
const body = await response.json().catch(() => null);
|
|
908
|
-
if (typeof body?.id !== "string"
|
|
909
|
-
throw new TasksApiError("resposta inv\xE1lida da cria\xE7\xE3o de run (id
|
|
908
|
+
if (typeof body?.id !== "string") {
|
|
909
|
+
throw new TasksApiError("resposta inv\xE1lida da cria\xE7\xE3o de run (id ausente)", 0);
|
|
910
910
|
}
|
|
911
911
|
return {
|
|
912
912
|
id: body.id,
|
|
913
|
-
task: body.task,
|
|
914
913
|
params: body.params && typeof body.params === "object" && !Array.isArray(body.params) ? body.params : {},
|
|
915
914
|
form: null,
|
|
916
915
|
source: typeof body.source === "string" ? body.source : "agent",
|
|
@@ -925,9 +924,9 @@ var TasksApi = class {
|
|
|
925
924
|
}
|
|
926
925
|
const valid = [];
|
|
927
926
|
for (const run of runs.slice(0, Math.max(0, max))) {
|
|
928
|
-
if (typeof run?.id !== "string"
|
|
927
|
+
if (typeof run?.id !== "string") continue;
|
|
929
928
|
const params = run.params && typeof run.params === "object" && !Array.isArray(run.params) ? run.params : {};
|
|
930
|
-
valid.push({ ...run, id: run.id,
|
|
929
|
+
valid.push({ ...run, id: run.id, params });
|
|
931
930
|
}
|
|
932
931
|
if (valid.length < runs.length) {
|
|
933
932
|
this.logger.warn(`poll retornou ${runs.length - valid.length} run(s) inv\xE1lida(s)/excedente(s) \u2014 descartadas`);
|
|
@@ -942,7 +941,7 @@ var TasksApi = class {
|
|
|
942
941
|
let response;
|
|
943
942
|
try {
|
|
944
943
|
response = await fetchWithRetry(
|
|
945
|
-
() => fetch(`${this.baseUrl}
|
|
944
|
+
() => fetch(`${this.baseUrl}/${encodeURIComponent(runId)}`, {
|
|
946
945
|
redirect: "error",
|
|
947
946
|
method: "PATCH",
|
|
948
947
|
headers: this.headers(),
|
|
@@ -964,7 +963,7 @@ var TasksApi = class {
|
|
|
964
963
|
* que a run já foi cancelada/finalizada, o que é aceitável nesse caminho.
|
|
965
964
|
*/
|
|
966
965
|
async updateRunOnce(runId, update, timeoutMs = 2e3) {
|
|
967
|
-
await fetch(`${this.baseUrl}
|
|
966
|
+
await fetch(`${this.baseUrl}/${encodeURIComponent(runId)}`, {
|
|
968
967
|
redirect: "error",
|
|
969
968
|
method: "PATCH",
|
|
970
969
|
headers: this.headers(),
|
|
@@ -981,7 +980,7 @@ var TasksApi = class {
|
|
|
981
980
|
let response;
|
|
982
981
|
try {
|
|
983
982
|
response = await fetchWithRetry(
|
|
984
|
-
() => fetch(`${this.baseUrl}
|
|
983
|
+
() => fetch(`${this.baseUrl}/${encodeURIComponent(runId)}/items`, {
|
|
985
984
|
redirect: "error",
|
|
986
985
|
method: "POST",
|
|
987
986
|
headers: this.headers(),
|
|
@@ -1011,7 +1010,7 @@ var TasksApi = class {
|
|
|
1011
1010
|
() => withRequestTimeout(
|
|
1012
1011
|
UPDATE_TIMEOUT_MS,
|
|
1013
1012
|
signal,
|
|
1014
|
-
(timeoutSignal) => fetch(`${this.baseUrl}
|
|
1013
|
+
(timeoutSignal) => fetch(`${this.baseUrl}/${encodeURIComponent(runId)}/attachments`, {
|
|
1015
1014
|
redirect: "error",
|
|
1016
1015
|
method: "POST",
|
|
1017
1016
|
headers: this.headers(),
|
|
@@ -1062,7 +1061,7 @@ var TasksApi = class {
|
|
|
1062
1061
|
/** Marca o anexo como enviado (ou falho). Best-effort — a leitura não depende disso. */
|
|
1063
1062
|
async confirmAttachment(runId, attachmentId, request) {
|
|
1064
1063
|
const response = await fetch(
|
|
1065
|
-
`${this.baseUrl}
|
|
1064
|
+
`${this.baseUrl}/${encodeURIComponent(runId)}/attachments/${encodeURIComponent(attachmentId)}`,
|
|
1066
1065
|
{
|
|
1067
1066
|
redirect: "error",
|
|
1068
1067
|
method: "PATCH",
|
|
@@ -1320,8 +1319,8 @@ function splitLogArgs(message, rest) {
|
|
|
1320
1319
|
if (text.length === 0) return { text: last.caption !== void 0 ? safeString(last.caption) : "captura de tela", image: last };
|
|
1321
1320
|
return { text: joinLogArgs(text[0], text.slice(1)), image: last };
|
|
1322
1321
|
}
|
|
1323
|
-
function printRunLog(
|
|
1324
|
-
const line = colorize(`[hobots
|
|
1322
|
+
function printRunLog(level, message) {
|
|
1323
|
+
const line = colorize(`[hobots] ${level.toUpperCase()}`, LOG_LEVEL_COLOR[level]);
|
|
1325
1324
|
const method2 = level === "warning" ? "warn" : level === "success" ? "info" : level;
|
|
1326
1325
|
console[method2](line, message);
|
|
1327
1326
|
}
|
|
@@ -1349,7 +1348,6 @@ var MAX_CANCEL_CHECK_INTERVAL_MS = 15e3;
|
|
|
1349
1348
|
var MAX_LOG_MESSAGE_CHARS = WIRE_LIMITS.MAX_LOG_MESSAGE_CHARS;
|
|
1350
1349
|
var MAX_LOGS_PER_PATCH = WIRE_LIMITS.MAX_LOGS_PER_PATCH;
|
|
1351
1350
|
var FLUSH_AT_BUFFERED = 20;
|
|
1352
|
-
var MAX_ITEMS_PER_RUN = WIRE_LIMITS.MAX_ITEMS_PER_RUN;
|
|
1353
1351
|
var MAX_ITEMS_PER_REQUEST = WIRE_LIMITS.MAX_ITEMS_PER_REQUEST;
|
|
1354
1352
|
var MAX_ITEM_MESSAGE_CHARS = WIRE_LIMITS.MAX_ITEM_MESSAGE_CHARS;
|
|
1355
1353
|
var MAX_ITEM_PAYLOAD_BYTES = WIRE_LIMITS.MAX_ITEM_PAYLOAD_BYTES;
|
|
@@ -1367,10 +1365,11 @@ function scrubPayloadValue(value, depth) {
|
|
|
1367
1365
|
return out;
|
|
1368
1366
|
}
|
|
1369
1367
|
var RunReporter = class {
|
|
1370
|
-
constructor(api, runId,
|
|
1368
|
+
constructor(api, runId, logger, tx, flushIntervalMs, cancelCheckIntervalMs, params = {}) {
|
|
1371
1369
|
this.api = api;
|
|
1372
1370
|
this.runId = runId;
|
|
1373
1371
|
this.logger = logger;
|
|
1372
|
+
this.tx = tx;
|
|
1374
1373
|
this.flushIntervalMs = flushIntervalMs ?? DEFAULT_LOG_FLUSH_INTERVAL_MS;
|
|
1375
1374
|
this.cancelCheckIntervalMs = Math.min(
|
|
1376
1375
|
MAX_CANCEL_CHECK_INTERVAL_MS,
|
|
@@ -1449,12 +1448,12 @@ var RunReporter = class {
|
|
|
1449
1448
|
const sleep2 = (ms) => cancelableSleep(ms, signal);
|
|
1450
1449
|
this.context = {
|
|
1451
1450
|
runId,
|
|
1452
|
-
task,
|
|
1453
1451
|
params,
|
|
1454
1452
|
log,
|
|
1455
1453
|
items,
|
|
1456
1454
|
progress,
|
|
1457
1455
|
transaction: tx,
|
|
1456
|
+
flush: (timeoutMs) => this.flush(timeoutMs),
|
|
1458
1457
|
signal,
|
|
1459
1458
|
throwIfCanceled: checkCanceled,
|
|
1460
1459
|
sleep: sleep2,
|
|
@@ -1464,6 +1463,7 @@ var RunReporter = class {
|
|
|
1464
1463
|
api;
|
|
1465
1464
|
runId;
|
|
1466
1465
|
logger;
|
|
1466
|
+
tx;
|
|
1467
1467
|
context;
|
|
1468
1468
|
buffer = [];
|
|
1469
1469
|
dead = false;
|
|
@@ -1481,7 +1481,6 @@ var RunReporter = class {
|
|
|
1481
1481
|
itemsChain = Promise.resolve();
|
|
1482
1482
|
itemsTimer;
|
|
1483
1483
|
itemSeq = 0;
|
|
1484
|
-
itemCount = 0;
|
|
1485
1484
|
/** Servidor sem o recurso (404) — para de tentar (e de avisar) nesta run. */
|
|
1486
1485
|
itemsUnsupported = false;
|
|
1487
1486
|
/** Cap de itens por run atingido (client-side ou 429) — degrada com um aviso só. */
|
|
@@ -1558,7 +1557,7 @@ var RunReporter = class {
|
|
|
1558
1557
|
const scrubbed = scrubSecrets(safeString(message));
|
|
1559
1558
|
const ts = nowISO();
|
|
1560
1559
|
const attachmentId = image3 === void 0 ? void 0 : this.queueAttachment(image3, ts);
|
|
1561
|
-
printRunLog(
|
|
1560
|
+
printRunLog(level, attachmentId ? `${scrubbed} [imagem]` : scrubbed);
|
|
1562
1561
|
this.push(level, scrubbed, true, attachmentId ? [attachmentId] : void 0, ts);
|
|
1563
1562
|
}
|
|
1564
1563
|
/** Bufferiza uma entrada de log sem eco no terminal (cap, truncate, flush). */
|
|
@@ -1604,6 +1603,21 @@ var RunReporter = class {
|
|
|
1604
1603
|
this.enqueue({ status: "running", logs: logs.slice(i, i + MAX_LOGS_PER_PATCH) });
|
|
1605
1604
|
}
|
|
1606
1605
|
}
|
|
1606
|
+
/**
|
|
1607
|
+
* `hobots.flush()`: o bot decide quando entregar o que já fez — steps raiz finalizadas
|
|
1608
|
+
* viram transactions parciais da run, e itens, logs e progresso pendentes saem já, sem
|
|
1609
|
+
* esperar o throttle. Aguarda a entrega com teto. Nunca lança (pode ir num `finally`).
|
|
1610
|
+
*/
|
|
1611
|
+
async flush(timeoutMs = ITEMS_DRAIN_MS) {
|
|
1612
|
+
this.tx.flush();
|
|
1613
|
+
this.flushItems();
|
|
1614
|
+
if (this.progressDirty) this.flushProgress();
|
|
1615
|
+
else this.flushLogs();
|
|
1616
|
+
await Promise.race([
|
|
1617
|
+
Promise.all([this.itemsChain, this.chain, this.tx.drain(timeoutMs)]).catch(() => void 0),
|
|
1618
|
+
new Promise((resolve2) => unrefTimer(setTimeout(resolve2, timeoutMs)))
|
|
1619
|
+
]);
|
|
1620
|
+
}
|
|
1607
1621
|
/** Primeiro PATCH (claimed → running). Retorna false se a run já morreu (cancelada). */
|
|
1608
1622
|
async reportRunning() {
|
|
1609
1623
|
this.enqueue({ status: "running" });
|
|
@@ -1714,14 +1728,6 @@ var RunReporter = class {
|
|
|
1714
1728
|
*/
|
|
1715
1729
|
addItem(status, message, payload, id, image3) {
|
|
1716
1730
|
if (this.itemsClosed || this.itemsUnsupported) return;
|
|
1717
|
-
if (this.itemCount >= MAX_ITEMS_PER_RUN) {
|
|
1718
|
-
if (!this.itemsQuotaExceeded) {
|
|
1719
|
-
this.itemsQuotaExceeded = true;
|
|
1720
|
-
this.log("warning", `itens descartados: limite de ${MAX_ITEMS_PER_RUN} por execu\xE7\xE3o atingido`);
|
|
1721
|
-
}
|
|
1722
|
-
return;
|
|
1723
|
-
}
|
|
1724
|
-
this.itemCount++;
|
|
1725
1731
|
let scrubbedPayload;
|
|
1726
1732
|
if (payload) {
|
|
1727
1733
|
scrubbedPayload = scrubPayloadValue(payload, 0);
|
|
@@ -2016,6 +2022,7 @@ function currentRunReporter() {
|
|
|
2016
2022
|
}
|
|
2017
2023
|
|
|
2018
2024
|
// src/transaction.ts
|
|
2025
|
+
var FLUSH_TRANSACTION_OP = "hobots.run.flush";
|
|
2019
2026
|
var MAX_STEP_MESSAGE_CHARS = 2e3;
|
|
2020
2027
|
function stepMessage(text) {
|
|
2021
2028
|
return truncate(scrubSecrets(text), MAX_STEP_MESSAGE_CHARS);
|
|
@@ -2203,7 +2210,7 @@ var Transaction = class {
|
|
|
2203
2210
|
if (this.attachmentSink) return this.attachmentSink(image3, nowISO());
|
|
2204
2211
|
if (!this.warnedNoAttachmentSink) {
|
|
2205
2212
|
this.warnedNoAttachmentSink = true;
|
|
2206
|
-
console.warn("[hobots] imagem de step descartada \u2014 s\xF3 \xE9 suportada dentro de uma execu\xE7\xE3o
|
|
2213
|
+
console.warn("[hobots] imagem de step descartada \u2014 s\xF3 \xE9 suportada dentro de uma execu\xE7\xE3o");
|
|
2207
2214
|
}
|
|
2208
2215
|
return void 0;
|
|
2209
2216
|
}
|
|
@@ -2237,6 +2244,60 @@ var Transaction = class {
|
|
|
2237
2244
|
this.name = name;
|
|
2238
2245
|
return this;
|
|
2239
2246
|
}
|
|
2247
|
+
/**
|
|
2248
|
+
* Envia já as steps raiz finalizadas (e todas as descendentes, fechando as que
|
|
2249
|
+
* ficaram abertas) como transactions parciais da mesma run (`op` hobots.run.flush,
|
|
2250
|
+
* `event_id` novo, até 200 steps cada) e as tira da memória. Steps raiz ainda abertas
|
|
2251
|
+
* ficam para o próximo flush ou para o `finish()`. É assim que um agente sempre
|
|
2252
|
+
* ligado (`start()`), cuja run não termina, entrega as etapas enquanto processa.
|
|
2253
|
+
* Devolve quantas steps foram enviadas.
|
|
2254
|
+
*/
|
|
2255
|
+
flush() {
|
|
2256
|
+
if (this.finished) return 0;
|
|
2257
|
+
const ready = /* @__PURE__ */ new Set();
|
|
2258
|
+
for (const step of this.steps) {
|
|
2259
|
+
if (step.parentStepId === this.stepId && step.endTimestamp) ready.add(step.stepId);
|
|
2260
|
+
}
|
|
2261
|
+
if (ready.size === 0) return 0;
|
|
2262
|
+
const batch = [];
|
|
2263
|
+
const remaining = [];
|
|
2264
|
+
for (const step of this.steps) {
|
|
2265
|
+
if (ready.has(step.stepId) || ready.has(step.parentStepId)) {
|
|
2266
|
+
ready.add(step.stepId);
|
|
2267
|
+
batch.push(step);
|
|
2268
|
+
} else {
|
|
2269
|
+
remaining.push(step);
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
for (const step of batch) step.finish();
|
|
2273
|
+
this.steps.splice(0, this.steps.length, ...remaining);
|
|
2274
|
+
if (this.client) {
|
|
2275
|
+
for (let i = 0; i < batch.length; i += MAX_STEPS) {
|
|
2276
|
+
const chunk = batch.slice(i, i + MAX_STEPS);
|
|
2277
|
+
const startTimestamp = chunk.reduce((min, step) => step.startTimestamp < min ? step.startTimestamp : min, chunk[0].startTimestamp);
|
|
2278
|
+
const status = chunk.some((step) => step.status === "error") ? "error" : "ok";
|
|
2279
|
+
this.client.sendTransaction(this.payload(uuid4(), FLUSH_TRANSACTION_OP, status, startTimestamp, nowISO(), chunk));
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
return batch.length;
|
|
2283
|
+
}
|
|
2284
|
+
/** Entrega já (com teto) o que está na fila do transport — as transactions parciais inclusive. */
|
|
2285
|
+
async drain(timeoutMs) {
|
|
2286
|
+
await this.client?.flush(timeoutMs);
|
|
2287
|
+
}
|
|
2288
|
+
payload(eventId, op, status, startTimestamp, timestamp, steps) {
|
|
2289
|
+
return {
|
|
2290
|
+
event_id: eventId,
|
|
2291
|
+
name: this.name,
|
|
2292
|
+
op,
|
|
2293
|
+
status,
|
|
2294
|
+
start_timestamp: startTimestamp,
|
|
2295
|
+
timestamp,
|
|
2296
|
+
steps: steps.map((step) => step.toJSON()),
|
|
2297
|
+
tags: Object.keys(this.tags).length > 0 ? { ...this.tags } : void 0,
|
|
2298
|
+
run_id: this.runId
|
|
2299
|
+
};
|
|
2300
|
+
}
|
|
2240
2301
|
finish() {
|
|
2241
2302
|
if (this.finished) return;
|
|
2242
2303
|
this.finished = true;
|
|
@@ -2251,31 +2312,27 @@ var Transaction = class {
|
|
|
2251
2312
|
this.status
|
|
2252
2313
|
);
|
|
2253
2314
|
if (!this.client) return;
|
|
2254
|
-
const
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
status
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
run_id: this.runId
|
|
2264
|
-
};
|
|
2265
|
-
this.client.sendTransaction(payload);
|
|
2315
|
+
const steps = this.steps;
|
|
2316
|
+
let first = 0;
|
|
2317
|
+
while (steps.length - first > MAX_STEPS) {
|
|
2318
|
+
const chunk = steps.slice(first, first + MAX_STEPS);
|
|
2319
|
+
const status = chunk.some((step) => step.status === "error") ? "error" : "ok";
|
|
2320
|
+
this.client.sendTransaction(this.payload(uuid4(), FLUSH_TRANSACTION_OP, status, chunk[0].startTimestamp, endTimestamp, chunk));
|
|
2321
|
+
first += MAX_STEPS;
|
|
2322
|
+
}
|
|
2323
|
+
this.client.sendTransaction(this.payload(this.eventId, this.op, this.status, this.startTimestamp, endTimestamp, steps.slice(first)));
|
|
2266
2324
|
}
|
|
2267
2325
|
};
|
|
2268
2326
|
|
|
2269
2327
|
// src/tasks/runner.ts
|
|
2270
2328
|
var DEFAULT_RETRY_DELAY_MS = 5e3;
|
|
2271
2329
|
async function executeRun(input) {
|
|
2272
|
-
const { run,
|
|
2273
|
-
const options =
|
|
2274
|
-
const tx = new Transaction(client,
|
|
2330
|
+
const { run, handler, api, client, logger, tracker } = input;
|
|
2331
|
+
const options = handler.options;
|
|
2332
|
+
const tx = new Transaction(client, "run", "hobots.run", run.id);
|
|
2275
2333
|
const reporter = new RunReporter(
|
|
2276
2334
|
api,
|
|
2277
2335
|
run.id,
|
|
2278
|
-
run.task,
|
|
2279
2336
|
logger,
|
|
2280
2337
|
tx,
|
|
2281
2338
|
options.logFlushIntervalMs,
|
|
@@ -2291,9 +2348,9 @@ async function executeRun(input) {
|
|
|
2291
2348
|
tx.finish();
|
|
2292
2349
|
return;
|
|
2293
2350
|
}
|
|
2294
|
-
logger.debug(`executando run ${run.id}
|
|
2351
|
+
logger.debug(`executando run ${run.id}`);
|
|
2295
2352
|
try {
|
|
2296
|
-
await runWithRunContext({ reporter }, () => invokeWithRetry(
|
|
2353
|
+
await runWithRunContext({ reporter }, () => invokeWithRetry(handler, run, reporter, logger));
|
|
2297
2354
|
await reporter.finalizeAttachments();
|
|
2298
2355
|
await reporter.finalizeItems();
|
|
2299
2356
|
warnIncompleteProgress(reporter);
|
|
@@ -2321,11 +2378,11 @@ function warnIncompleteProgress(reporter) {
|
|
|
2321
2378
|
`execu\xE7\xE3o conclu\xEDda com progresso incompleto: ${snapshot.current}/${snapshot.total} \u2014 o handler terminou sem processar (ou sem avan\xE7ar) todos os itens previstos`
|
|
2322
2379
|
);
|
|
2323
2380
|
}
|
|
2324
|
-
function invokeWithRetry(
|
|
2325
|
-
const { retries, retryDelayMs } =
|
|
2381
|
+
function invokeWithRetry(agent, run, reporter, logger) {
|
|
2382
|
+
const { retries, retryDelayMs } = agent.options;
|
|
2326
2383
|
const total = retries + 1;
|
|
2327
2384
|
const ctx = reporter.context;
|
|
2328
|
-
return retryWithBackoff(() => invokeWithTimeout(
|
|
2385
|
+
return retryWithBackoff(() => invokeWithTimeout(agent, run, reporter, logger), {
|
|
2329
2386
|
retries,
|
|
2330
2387
|
delayMs: (attempt) => resolveDelay(retryDelayMs, attempt, DEFAULT_RETRY_DELAY_MS),
|
|
2331
2388
|
shouldRetry: () => !reporter.isDead,
|
|
@@ -2339,9 +2396,9 @@ function invokeWithRetry(task, run, reporter, logger) {
|
|
|
2339
2396
|
}
|
|
2340
2397
|
var TIMEOUT_SENTINEL = /* @__PURE__ */ Symbol("task-timeout");
|
|
2341
2398
|
var CANCEL_SENTINEL = /* @__PURE__ */ Symbol("task-canceled");
|
|
2342
|
-
async function invokeWithTimeout(
|
|
2343
|
-
const { timeoutMs, cancelGraceMs } =
|
|
2344
|
-
const handler =
|
|
2399
|
+
async function invokeWithTimeout(agent, run, reporter, logger) {
|
|
2400
|
+
const { timeoutMs, cancelGraceMs } = agent.options;
|
|
2401
|
+
const handler = agent.handler;
|
|
2345
2402
|
const invocation = Promise.resolve().then(() => handler(run.params, reporter.context));
|
|
2346
2403
|
const races = [];
|
|
2347
2404
|
if (timeoutMs && timeoutMs > 0) {
|
|
@@ -2398,7 +2455,7 @@ var TaskAgent = class {
|
|
|
2398
2455
|
this.logger = options.logger;
|
|
2399
2456
|
this.api = options.api;
|
|
2400
2457
|
this.client = options.client;
|
|
2401
|
-
this.
|
|
2458
|
+
this.handler = options.handler;
|
|
2402
2459
|
this.tracker = options.tracker;
|
|
2403
2460
|
this.pollIntervalMs = Math.max(MIN_POLL_INTERVAL_MS, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
|
|
2404
2461
|
this.backoff = new Backoff(this.logger, "poll", this.pollIntervalMs);
|
|
@@ -2408,7 +2465,7 @@ var TaskAgent = class {
|
|
|
2408
2465
|
pollIntervalMs;
|
|
2409
2466
|
api;
|
|
2410
2467
|
client;
|
|
2411
|
-
|
|
2468
|
+
handler;
|
|
2412
2469
|
tracker;
|
|
2413
2470
|
backoff;
|
|
2414
2471
|
state = "idle";
|
|
@@ -2424,10 +2481,10 @@ var TaskAgent = class {
|
|
|
2424
2481
|
throw new Error("[hobots] start do agente durante o stop \u2014 aguarde o stop terminar");
|
|
2425
2482
|
}
|
|
2426
2483
|
this.state = "started";
|
|
2427
|
-
this.logger.debug(`polling iniciado (
|
|
2484
|
+
this.logger.debug(`polling iniciado (concorr\xEAncia ${this.handler.options.concurrency})`);
|
|
2428
2485
|
this.schedule(0);
|
|
2429
2486
|
}
|
|
2430
|
-
/**
|
|
2487
|
+
/** Antecipa o próximo poll para já. */
|
|
2431
2488
|
wake() {
|
|
2432
2489
|
if (this.state !== "started") return;
|
|
2433
2490
|
if (this.timer) {
|
|
@@ -2458,21 +2515,14 @@ var TaskAgent = class {
|
|
|
2458
2515
|
void this.tick();
|
|
2459
2516
|
}, delayMs);
|
|
2460
2517
|
}
|
|
2461
|
-
/** Slots livres
|
|
2518
|
+
/** Slots livres do agente — vira o `max` do poll (nunca reivindica trabalho para deixar parado). */
|
|
2462
2519
|
freeSlots() {
|
|
2463
|
-
const
|
|
2464
|
-
|
|
2465
|
-
for (const task of this.registry.remoteTasks()) {
|
|
2466
|
-
const free = task.options.concurrency - this.tracker.activeFor(task.slug);
|
|
2467
|
-
if (free <= 0) continue;
|
|
2468
|
-
tasks.push(task.slug);
|
|
2469
|
-
max += free;
|
|
2470
|
-
}
|
|
2471
|
-
return { tasks, max: Math.min(MAX_CLAIM_PER_POLL, max) };
|
|
2520
|
+
const free = this.handler.options.concurrency - this.tracker.activeCount;
|
|
2521
|
+
return Math.min(MAX_CLAIM_PER_POLL, Math.max(0, free));
|
|
2472
2522
|
}
|
|
2473
2523
|
async tick() {
|
|
2474
2524
|
if (this.state !== "started") return;
|
|
2475
|
-
const
|
|
2525
|
+
const max = this.freeSlots();
|
|
2476
2526
|
if (max === 0) {
|
|
2477
2527
|
this.schedule(this.pollIntervalMs);
|
|
2478
2528
|
return;
|
|
@@ -2482,11 +2532,9 @@ var TaskAgent = class {
|
|
|
2482
2532
|
const response = await this.api.poll(
|
|
2483
2533
|
{
|
|
2484
2534
|
// identidade canônica do agente no painel — o mesmo instanceId do heartbeat,
|
|
2485
|
-
// então os dois canais convergem em um agente
|
|
2486
|
-
// tasks (register + start); o filtro `tasks` abaixo só as que este poll pode rodar.
|
|
2535
|
+
// então os dois canais convergem em um agente
|
|
2487
2536
|
instance: this.options.instanceInfo(),
|
|
2488
|
-
max
|
|
2489
|
-
tasks
|
|
2537
|
+
max
|
|
2490
2538
|
},
|
|
2491
2539
|
this.pollAbort.signal
|
|
2492
2540
|
);
|
|
@@ -2510,17 +2558,7 @@ var TaskAgent = class {
|
|
|
2510
2558
|
this.activeRuns.add(promise);
|
|
2511
2559
|
}
|
|
2512
2560
|
async executeClaimed(run) {
|
|
2513
|
-
|
|
2514
|
-
if (!task || task.mode !== "remote") {
|
|
2515
|
-
const tx = new Transaction(this.client, `run:${run.task}`, "hobots.task", run.id);
|
|
2516
|
-
const reporter = new RunReporter(this.api, run.id, run.task, this.logger, tx);
|
|
2517
|
-
reporter.reportFailure(toRunError(Object.assign(new Error(`tarefa n\xE3o registrada no agente: ${run.task}`), { name: "HandlerNotFound" })));
|
|
2518
|
-
await reporter.finalize();
|
|
2519
|
-
tx.setStatus("error");
|
|
2520
|
-
tx.finish();
|
|
2521
|
-
return;
|
|
2522
|
-
}
|
|
2523
|
-
await executeRun({ run, task, api: this.api, client: this.client, logger: this.logger, tracker: this.tracker });
|
|
2561
|
+
await executeRun({ run, handler: this.handler, api: this.api, client: this.client, logger: this.logger, tracker: this.tracker });
|
|
2524
2562
|
}
|
|
2525
2563
|
};
|
|
2526
2564
|
|
|
@@ -2528,11 +2566,11 @@ var TaskAgent = class {
|
|
|
2528
2566
|
var KEY_RE = /^[a-zA-Z0-9_.-]{1,100}$/;
|
|
2529
2567
|
var MAX_COLUMNS = WIRE_LIMITS.MAX_ITEM_COLUMNS;
|
|
2530
2568
|
var MAX_LABEL_CHARS = WIRE_LIMITS.MAX_ITEM_COLUMN_LABEL_CHARS;
|
|
2531
|
-
function normalizeItemColumns(
|
|
2569
|
+
function normalizeItemColumns(input, logger) {
|
|
2532
2570
|
if (input === void 0 || input === null) return void 0;
|
|
2533
2571
|
const columns = input.columns;
|
|
2534
2572
|
if (typeof input !== "object" || !Array.isArray(columns)) {
|
|
2535
|
-
logger.warn(`
|
|
2573
|
+
logger.warn(`items.columns precisa ser uma lista \u2014 declara\xE7\xE3o de colunas ignorada`);
|
|
2536
2574
|
return void 0;
|
|
2537
2575
|
}
|
|
2538
2576
|
const result = [];
|
|
@@ -2542,15 +2580,15 @@ function normalizeItemColumns(slug, input, logger) {
|
|
|
2542
2580
|
const rawLabel = typeof entry === "object" && entry !== null ? entry.label : void 0;
|
|
2543
2581
|
const key = typeof rawKey === "string" ? rawKey.trim() : "";
|
|
2544
2582
|
if (!KEY_RE.test(key)) {
|
|
2545
|
-
logger.warn(`
|
|
2583
|
+
logger.warn(`coluna de item inv\xE1lida ignorada (${JSON.stringify(rawKey)}) \u2014 use letras, n\xFAmeros, ponto, h\xEDfen e underline`);
|
|
2546
2584
|
continue;
|
|
2547
2585
|
}
|
|
2548
2586
|
if (seen.has(key)) {
|
|
2549
|
-
logger.warn(`
|
|
2587
|
+
logger.warn(`coluna de item "${key}" repetida ignorada`);
|
|
2550
2588
|
continue;
|
|
2551
2589
|
}
|
|
2552
2590
|
if (result.length >= MAX_COLUMNS) {
|
|
2553
|
-
logger.warn(`
|
|
2591
|
+
logger.warn(`no m\xE1ximo ${MAX_COLUMNS} colunas de item \u2014 "${key}" em diante ignoradas`);
|
|
2554
2592
|
break;
|
|
2555
2593
|
}
|
|
2556
2594
|
seen.add(key);
|
|
@@ -2562,15 +2600,15 @@ function normalizeItemColumns(slug, input, logger) {
|
|
|
2562
2600
|
|
|
2563
2601
|
// src/tasks/local.ts
|
|
2564
2602
|
var LocalRunner = class {
|
|
2565
|
-
constructor(
|
|
2566
|
-
this.
|
|
2603
|
+
constructor(handler, deps) {
|
|
2604
|
+
this.handler = handler;
|
|
2567
2605
|
this.deps = deps;
|
|
2568
|
-
this.backoff = new Backoff(deps.logger,
|
|
2606
|
+
this.backoff = new Backoff(deps.logger, "cria\xE7\xE3o da run");
|
|
2569
2607
|
this.done = new Promise((resolve2) => {
|
|
2570
2608
|
this.resolveDone = resolve2;
|
|
2571
2609
|
});
|
|
2572
2610
|
}
|
|
2573
|
-
|
|
2611
|
+
handler;
|
|
2574
2612
|
deps;
|
|
2575
2613
|
abort = new AbortController();
|
|
2576
2614
|
backoff;
|
|
@@ -2581,17 +2619,17 @@ var LocalRunner = class {
|
|
|
2581
2619
|
try {
|
|
2582
2620
|
const run = await this.createRun();
|
|
2583
2621
|
if (!run) return;
|
|
2584
|
-
this.deps.logger.debug(`run ${run.id} aberta
|
|
2622
|
+
this.deps.logger.debug(`run ${run.id} aberta (modo start)`);
|
|
2585
2623
|
await executeRun({
|
|
2586
2624
|
run,
|
|
2587
|
-
|
|
2625
|
+
handler: this.handler,
|
|
2588
2626
|
api: this.deps.api,
|
|
2589
2627
|
client: this.deps.client,
|
|
2590
2628
|
logger: this.deps.logger,
|
|
2591
2629
|
tracker: this.deps.tracker
|
|
2592
2630
|
});
|
|
2593
2631
|
} catch (error) {
|
|
2594
|
-
this.deps.logger.error(
|
|
2632
|
+
this.deps.logger.error("erro inesperado na execu\xE7\xE3o do agente:", error);
|
|
2595
2633
|
} finally {
|
|
2596
2634
|
this.resolveDone();
|
|
2597
2635
|
}
|
|
@@ -2604,7 +2642,7 @@ var LocalRunner = class {
|
|
|
2604
2642
|
for (; ; ) {
|
|
2605
2643
|
if (this.abort.signal.aborted) return void 0;
|
|
2606
2644
|
try {
|
|
2607
|
-
return await this.deps.api.createRun({
|
|
2645
|
+
return await this.deps.api.createRun({ instance: this.deps.instanceInfo() }, this.abort.signal);
|
|
2608
2646
|
} catch (error) {
|
|
2609
2647
|
if (this.abort.signal.aborted) return void 0;
|
|
2610
2648
|
await abortableSleep(this.backoff.next(error), this.abort.signal);
|
|
@@ -2614,45 +2652,6 @@ var LocalRunner = class {
|
|
|
2614
2652
|
};
|
|
2615
2653
|
|
|
2616
2654
|
// src/tasks/registry.ts
|
|
2617
|
-
var TaskRegistry = class {
|
|
2618
|
-
tasks = /* @__PURE__ */ new Map();
|
|
2619
|
-
add(task) {
|
|
2620
|
-
if (this.tasks.has(task.slug)) {
|
|
2621
|
-
throw new Error(`[hobots] task "${task.slug}" j\xE1 registrada \u2014 cada slug s\xF3 pode ter um handler (register ou start)`);
|
|
2622
|
-
}
|
|
2623
|
-
this.tasks.set(task.slug, task);
|
|
2624
|
-
}
|
|
2625
|
-
get(slug) {
|
|
2626
|
-
return this.tasks.get(slug);
|
|
2627
|
-
}
|
|
2628
|
-
get size() {
|
|
2629
|
-
return this.tasks.size;
|
|
2630
|
-
}
|
|
2631
|
-
/** Tasks que chegam pelo poll (register). */
|
|
2632
|
-
remoteTasks() {
|
|
2633
|
-
return [...this.tasks.values()].filter((task) => task.mode === "remote");
|
|
2634
|
-
}
|
|
2635
|
-
/** Tasks abertas pelo próprio bot (start). */
|
|
2636
|
-
localTasks() {
|
|
2637
|
-
return [...this.tasks.values()].filter((task) => task.mode === "local");
|
|
2638
|
-
}
|
|
2639
|
-
/** Todos os slugs, para a presença da instância (`instance.tasks`). */
|
|
2640
|
-
allSlugs() {
|
|
2641
|
-
return [...this.tasks.keys()];
|
|
2642
|
-
}
|
|
2643
|
-
/** Colunas de itens declaradas no código, por slug (`instance.task_items`); undefined se nenhuma task declarou. */
|
|
2644
|
-
itemDeclarations() {
|
|
2645
|
-
let result;
|
|
2646
|
-
for (const task of this.tasks.values()) {
|
|
2647
|
-
if (!task.options.itemColumns) continue;
|
|
2648
|
-
(result ??= {})[task.slug] = { attributes: task.options.itemColumns };
|
|
2649
|
-
}
|
|
2650
|
-
return result;
|
|
2651
|
-
}
|
|
2652
|
-
clear() {
|
|
2653
|
-
this.tasks.clear();
|
|
2654
|
-
}
|
|
2655
|
-
};
|
|
2656
2655
|
var RunTracker = class {
|
|
2657
2656
|
constructor(api) {
|
|
2658
2657
|
this.api = api;
|
|
@@ -2673,12 +2672,6 @@ var RunTracker = class {
|
|
|
2673
2672
|
get activeCount() {
|
|
2674
2673
|
return this.reporters.size;
|
|
2675
2674
|
}
|
|
2676
|
-
/** Runs ativas de uma task (concorrência por task no poll). */
|
|
2677
|
-
activeFor(slug) {
|
|
2678
|
-
let count = 0;
|
|
2679
|
-
for (const reporter of this.reporters) if (reporter.context.task === slug) count++;
|
|
2680
|
-
return count;
|
|
2681
|
-
}
|
|
2682
2675
|
/** A única run ativa, quando há exatamente uma — o que `hobots.*` usa fora de um contexto async. */
|
|
2683
2676
|
singleActive() {
|
|
2684
2677
|
if (this.reporters.size !== 1) return void 0;
|
|
@@ -2767,40 +2760,13 @@ var DEFAULT_RETRIES2 = 1;
|
|
|
2767
2760
|
var DEFAULT_RETRY_DELAY_MS2 = 5e3;
|
|
2768
2761
|
var DEFAULT_CANCEL_GRACE_MS = 1e4;
|
|
2769
2762
|
var MAX_CONCURRENCY = 5;
|
|
2770
|
-
var MAX_SLUG_CHARS = 255;
|
|
2771
|
-
var PROCESS_LEVEL_KEYS = [
|
|
2772
|
-
"clientSecret",
|
|
2773
|
-
"instanceId",
|
|
2774
|
-
"environment",
|
|
2775
|
-
"release",
|
|
2776
|
-
"tags",
|
|
2777
|
-
"debug",
|
|
2778
|
-
"machineMetrics",
|
|
2779
|
-
"captureUnhandled",
|
|
2780
|
-
"installExitHandlers"
|
|
2781
|
-
];
|
|
2782
2763
|
var runtime;
|
|
2783
2764
|
var warnedNoRuntime = false;
|
|
2784
2765
|
function hostname() {
|
|
2785
2766
|
if (!isNode()) return void 0;
|
|
2786
2767
|
return process.env.HOSTNAME || process.env.COMPUTERNAME || void 0;
|
|
2787
2768
|
}
|
|
2788
|
-
function
|
|
2789
|
-
if (a === b) return true;
|
|
2790
|
-
if (a && b && typeof a === "object" && typeof b === "object") return JSON.stringify(a) === JSON.stringify(b);
|
|
2791
|
-
return false;
|
|
2792
|
-
}
|
|
2793
|
-
function ensureRuntime(options) {
|
|
2794
|
-
if (runtime) {
|
|
2795
|
-
for (const key of PROCESS_LEVEL_KEYS) {
|
|
2796
|
-
if (options[key] !== void 0 && !sameValue(options[key], runtime.options[key])) {
|
|
2797
|
-
runtime.logger.warn(
|
|
2798
|
-
`op\xE7\xE3o "${key}" divergente ignorada \u2014 o primeiro register()/start() define as op\xE7\xF5es do processo`
|
|
2799
|
-
);
|
|
2800
|
-
}
|
|
2801
|
-
}
|
|
2802
|
-
return runtime;
|
|
2803
|
-
}
|
|
2769
|
+
function createRuntime(options, handler) {
|
|
2804
2770
|
const config = resolveConfig({ clientSecret: options.clientSecret });
|
|
2805
2771
|
const logger = makeLogger(options.debug ?? false);
|
|
2806
2772
|
const environment = resolveEnvironmentOption(options.environment);
|
|
@@ -2808,19 +2774,19 @@ function ensureRuntime(options) {
|
|
|
2808
2774
|
{ environment, release: options.release, tags: options.tags, debug: options.debug },
|
|
2809
2775
|
config
|
|
2810
2776
|
);
|
|
2811
|
-
const api = new TasksApi({ baseUrl: config.
|
|
2812
|
-
|
|
2777
|
+
const api = new TasksApi({ baseUrl: config.runsBaseUrl, key: config.key, logger });
|
|
2778
|
+
const rt = {
|
|
2813
2779
|
options: { ...options, clientSecret: void 0 },
|
|
2814
2780
|
config,
|
|
2815
2781
|
client,
|
|
2816
2782
|
logger,
|
|
2817
2783
|
api,
|
|
2818
|
-
|
|
2784
|
+
handler,
|
|
2819
2785
|
tracker: new RunTracker(api),
|
|
2820
2786
|
sampler: void 0,
|
|
2821
2787
|
heartbeat: void 0,
|
|
2822
2788
|
agent: void 0,
|
|
2823
|
-
|
|
2789
|
+
local: void 0,
|
|
2824
2790
|
instanceId: resolveInstanceId(options.instanceId),
|
|
2825
2791
|
environment,
|
|
2826
2792
|
pollIntervalMs: void 0,
|
|
@@ -2830,24 +2796,19 @@ function ensureRuntime(options) {
|
|
|
2830
2796
|
uninstallGlobalHandlers: options.captureUnhandled !== false ? installGlobalHandlers(client) : void 0,
|
|
2831
2797
|
uninstallShutdown: void 0
|
|
2832
2798
|
};
|
|
2833
|
-
runtime.options.clientSecret = options.clientSecret;
|
|
2834
2799
|
logger.debug("inicializado", { endpoint: config.ingestEndpoint });
|
|
2835
|
-
return
|
|
2800
|
+
return rt;
|
|
2836
2801
|
}
|
|
2837
|
-
function
|
|
2838
|
-
if (typeof slug !== "string" || slug.trim().length === 0 || slug.length > MAX_SLUG_CHARS) {
|
|
2839
|
-
throw new Error(`[hobots] ${fn}(): o slug da task precisa ser uma string de 1 a ${MAX_SLUG_CHARS} caracteres`);
|
|
2840
|
-
}
|
|
2802
|
+
function validateHandler(handler, fn) {
|
|
2841
2803
|
if (typeof handler !== "function") {
|
|
2842
|
-
throw new Error(`[hobots] ${fn}(
|
|
2804
|
+
throw new Error(`[hobots] ${fn}(): o handler precisa ser uma fun\xE7\xE3o \u2014 ${fn}(async (params) => { ... })`);
|
|
2843
2805
|
}
|
|
2844
|
-
return slug.trim();
|
|
2845
2806
|
}
|
|
2846
2807
|
function clampConcurrency(value) {
|
|
2847
2808
|
if (value === void 0 || !Number.isFinite(value)) return 1;
|
|
2848
2809
|
return Math.min(MAX_CONCURRENCY, Math.max(1, Math.floor(value)));
|
|
2849
2810
|
}
|
|
2850
|
-
function
|
|
2811
|
+
function resolveRunOptions(options, mode, logger) {
|
|
2851
2812
|
const retries = options.retries === void 0 || !Number.isFinite(options.retries) ? DEFAULT_RETRIES2 : Math.max(0, Math.floor(options.retries));
|
|
2852
2813
|
return {
|
|
2853
2814
|
retries,
|
|
@@ -2857,7 +2818,7 @@ function resolveTaskOptions(slug, options, mode, logger) {
|
|
|
2857
2818
|
logFlushIntervalMs: options.logFlushIntervalMs,
|
|
2858
2819
|
cancelCheckIntervalMs: options.cancelCheckIntervalMs,
|
|
2859
2820
|
cancelGraceMs: options.cancelGraceMs ?? DEFAULT_CANCEL_GRACE_MS,
|
|
2860
|
-
itemColumns: normalizeItemColumns(
|
|
2821
|
+
itemColumns: normalizeItemColumns(options.items, logger)
|
|
2861
2822
|
};
|
|
2862
2823
|
}
|
|
2863
2824
|
function instanceInfo(rt) {
|
|
@@ -2867,10 +2828,10 @@ function instanceInfo(rt) {
|
|
|
2867
2828
|
host: hostname(),
|
|
2868
2829
|
// ambiente da instância: junto do heartbeat, é como o servidor sabe onde o robô roda
|
|
2869
2830
|
environment: rt.environment,
|
|
2870
|
-
//
|
|
2871
|
-
|
|
2831
|
+
// como esta instância trabalha — o painel mostra "sob demanda" / "sempre ligado"
|
|
2832
|
+
mode: rt.handler.mode === "remote" ? "on_demand" : "always_on",
|
|
2872
2833
|
// colunas da aba "itens" declaradas no código — a plataforma usa enquanto o usuário não personalizar
|
|
2873
|
-
|
|
2834
|
+
items: rt.handler.options.itemColumns ? { attributes: rt.handler.options.itemColumns } : void 0,
|
|
2874
2835
|
// o servidor calcula a tolerância de offline (3×) a partir disto em agentes que fazem
|
|
2875
2836
|
// poll; clampado à faixa aceita pelo schema (1s..15min)
|
|
2876
2837
|
poll_interval_ms: rt.agent ? Math.min(Math.max(rt.agent.pollIntervalMs, 1e3), 9e5) : void 0,
|
|
@@ -2883,22 +2844,22 @@ function ensureAgent(rt) {
|
|
|
2883
2844
|
api: rt.api,
|
|
2884
2845
|
client: rt.client,
|
|
2885
2846
|
logger: rt.logger,
|
|
2886
|
-
|
|
2847
|
+
handler: rt.handler,
|
|
2887
2848
|
tracker: rt.tracker,
|
|
2888
2849
|
instanceInfo: () => instanceInfo(rt),
|
|
2889
2850
|
pollIntervalMs: rt.pollIntervalMs
|
|
2890
2851
|
});
|
|
2891
2852
|
return rt.agent;
|
|
2892
2853
|
}
|
|
2893
|
-
function launchLocal(rt
|
|
2894
|
-
const runner = new LocalRunner(
|
|
2854
|
+
function launchLocal(rt) {
|
|
2855
|
+
const runner = new LocalRunner(rt.handler, {
|
|
2895
2856
|
api: rt.api,
|
|
2896
2857
|
client: rt.client,
|
|
2897
2858
|
logger: rt.logger,
|
|
2898
2859
|
tracker: rt.tracker,
|
|
2899
2860
|
instanceInfo: () => instanceInfo(rt)
|
|
2900
2861
|
});
|
|
2901
|
-
rt.
|
|
2862
|
+
rt.local = runner;
|
|
2902
2863
|
void runner.run();
|
|
2903
2864
|
}
|
|
2904
2865
|
async function boot(rt) {
|
|
@@ -2920,8 +2881,8 @@ async function boot(rt) {
|
|
|
2920
2881
|
if (rt.options.installExitHandlers !== false) {
|
|
2921
2882
|
rt.uninstallShutdown = installShutdownHandlers(rt.tracker);
|
|
2922
2883
|
}
|
|
2923
|
-
if (rt.
|
|
2924
|
-
|
|
2884
|
+
if (rt.handler.mode === "remote") ensureAgent(rt).start();
|
|
2885
|
+
else launchLocal(rt);
|
|
2925
2886
|
}
|
|
2926
2887
|
function scheduleBoot(rt) {
|
|
2927
2888
|
if (rt.bootScheduled) return;
|
|
@@ -2930,34 +2891,23 @@ function scheduleBoot(rt) {
|
|
|
2930
2891
|
rt.bootPromise = boot(rt).catch((error) => rt.logger.error("falha ao iniciar o SDK:", error));
|
|
2931
2892
|
});
|
|
2932
2893
|
}
|
|
2933
|
-
function
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
if (rt.pollIntervalMs === void 0 && !rt.agent) rt.pollIntervalMs = options.pollIntervalMs;
|
|
2938
|
-
else if (rt.pollIntervalMs !== options.pollIntervalMs) {
|
|
2939
|
-
rt.logger.warn('op\xE7\xE3o "pollIntervalMs" divergente ignorada \u2014 o primeiro register() define o intervalo do poll');
|
|
2940
|
-
}
|
|
2941
|
-
}
|
|
2942
|
-
const registered = { slug, mode, handler, options: resolveTaskOptions(slug, options, mode, rt.logger) };
|
|
2943
|
-
rt.registry.add(registered);
|
|
2944
|
-
if (!rt.booted) {
|
|
2945
|
-
scheduleBoot(rt);
|
|
2946
|
-
return;
|
|
2947
|
-
}
|
|
2948
|
-
if (mode === "remote") {
|
|
2949
|
-
const agent = ensureAgent(rt);
|
|
2950
|
-
agent.start();
|
|
2951
|
-
agent.wake();
|
|
2952
|
-
} else {
|
|
2953
|
-
launchLocal(rt, registered);
|
|
2894
|
+
function startAgent(fn, mode, handler, options) {
|
|
2895
|
+
validateHandler(handler, fn);
|
|
2896
|
+
if (runtime) {
|
|
2897
|
+
throw new Error(`[hobots] ${fn}(): o agente j\xE1 foi iniciado \u2014 register()/start() s\xF3 pode ser chamado uma vez por processo (chame hobots.close() antes de iniciar de novo)`);
|
|
2954
2898
|
}
|
|
2899
|
+
const logger = makeLogger(options.debug ?? false);
|
|
2900
|
+
const agentHandler = { mode, handler, options: resolveRunOptions(options, mode, logger) };
|
|
2901
|
+
const rt = createRuntime(options, agentHandler);
|
|
2902
|
+
if (mode === "remote" && options.pollIntervalMs !== void 0) rt.pollIntervalMs = options.pollIntervalMs;
|
|
2903
|
+
runtime = rt;
|
|
2904
|
+
scheduleBoot(rt);
|
|
2955
2905
|
}
|
|
2956
|
-
function register(
|
|
2957
|
-
|
|
2906
|
+
function register(handler, options = {}) {
|
|
2907
|
+
startAgent("register", "remote", handler, options);
|
|
2958
2908
|
}
|
|
2959
|
-
function start(
|
|
2960
|
-
|
|
2909
|
+
function start(handler, options = {}) {
|
|
2910
|
+
startAgent("start", "local", handler, options);
|
|
2961
2911
|
}
|
|
2962
2912
|
function currentReporter() {
|
|
2963
2913
|
return currentRunReporter() ?? runtime?.tracker.singleActive();
|
|
@@ -2971,7 +2921,7 @@ function toRunLogLevel(type) {
|
|
|
2971
2921
|
function forwardAttachment(rt, attachment, message, type) {
|
|
2972
2922
|
const reporter = currentReporter();
|
|
2973
2923
|
if (!reporter) {
|
|
2974
|
-
rt.logger.warn("attachment descartado \u2014 s\xF3 \xE9 suportado dentro de uma execu\xE7\xE3o
|
|
2924
|
+
rt.logger.warn("attachment descartado \u2014 s\xF3 \xE9 suportado dentro de uma execu\xE7\xE3o");
|
|
2975
2925
|
return;
|
|
2976
2926
|
}
|
|
2977
2927
|
const withMeta = typeof attachment === "object" && !isImageAttachment(attachment) && "image" in attachment ? image(attachment.image, { caption: attachment.caption, contentType: attachment.contentType }) : attachment;
|
|
@@ -2982,7 +2932,7 @@ function captureException(error, hint) {
|
|
|
2982
2932
|
if (!rt) {
|
|
2983
2933
|
if (!warnedNoRuntime) {
|
|
2984
2934
|
warnedNoRuntime = true;
|
|
2985
|
-
console.warn("[hobots] captureException antes de
|
|
2935
|
+
console.warn("[hobots] captureException antes de iniciar o agente (register/start) \u2014 evento descartado");
|
|
2986
2936
|
}
|
|
2987
2937
|
return void 0;
|
|
2988
2938
|
}
|
|
@@ -2998,7 +2948,7 @@ async function close(timeoutMs = 3e4) {
|
|
|
2998
2948
|
runtime = void 0;
|
|
2999
2949
|
await rt.bootPromise;
|
|
3000
2950
|
if (rt.agent) await rt.agent.stop(0);
|
|
3001
|
-
|
|
2951
|
+
rt.local?.stop();
|
|
3002
2952
|
const idle = await rt.tracker.waitIdle(timeoutMs);
|
|
3003
2953
|
if (!idle) {
|
|
3004
2954
|
rt.logger.warn(`${rt.tracker.activeCount} run(s) ainda em execu\xE7\xE3o ap\xF3s ${timeoutMs}ms \u2014 abandonando a espera`);
|
|
@@ -3016,15 +2966,14 @@ var warned = /* @__PURE__ */ new Set();
|
|
|
3016
2966
|
function warnOutsideRun(member) {
|
|
3017
2967
|
if (warned.has(member)) return;
|
|
3018
2968
|
warned.add(member);
|
|
3019
|
-
const message = `hobots.${member} usado fora de uma execu\xE7\xE3o
|
|
2969
|
+
const message = `hobots.${member} usado fora de uma execu\xE7\xE3o \u2014 ignorado (inicie o agente com register()/start() e use dentro do handler)`;
|
|
3020
2970
|
const logger = activeLogger();
|
|
3021
2971
|
if (logger) logger.warn(message);
|
|
3022
2972
|
else console.warn(`[hobots] ${message}`);
|
|
3023
2973
|
}
|
|
3024
|
-
var FALLBACK_TASK = "hobots";
|
|
3025
2974
|
var fallbackLogAt = (level) => (message, ...rest) => {
|
|
3026
2975
|
const { text, image: dropped } = splitLogArgs(message, rest);
|
|
3027
|
-
printRunLog(
|
|
2976
|
+
printRunLog(level, dropped ? `${text} [imagem descartada]` : text);
|
|
3028
2977
|
};
|
|
3029
2978
|
var fallbackLog = {
|
|
3030
2979
|
debug: fallbackLogAt("debug"),
|
|
@@ -3041,7 +2990,7 @@ var fallbackEachSink = {
|
|
|
3041
2990
|
advance: noop,
|
|
3042
2991
|
checkCanceled: noop,
|
|
3043
2992
|
isAborted: () => false,
|
|
3044
|
-
logError: (message) => printRunLog(
|
|
2993
|
+
logError: (message) => printRunLog("error", message)
|
|
3045
2994
|
};
|
|
3046
2995
|
var fallbackItems = {
|
|
3047
2996
|
add: noop,
|
|
@@ -3068,7 +3017,6 @@ function method(value) {
|
|
|
3068
3017
|
var hobots = Object.defineProperties({}, {
|
|
3069
3018
|
// ── contexto da run atual (getters) ─────────────────────────────────────────
|
|
3070
3019
|
runId: getter("runId", () => ""),
|
|
3071
|
-
task: getter("task", () => ""),
|
|
3072
3020
|
params: getter("params", () => ({})),
|
|
3073
3021
|
log: getter("log", () => fallbackLog),
|
|
3074
3022
|
items: getter("items", () => fallbackItems),
|
|
@@ -3077,17 +3025,18 @@ var hobots = Object.defineProperties({}, {
|
|
|
3077
3025
|
signal: getter("signal", () => neverAborts),
|
|
3078
3026
|
throwIfCanceled: method(() => resolve("throwIfCanceled")?.throwIfCanceled()),
|
|
3079
3027
|
sleep: method((ms) => resolve("sleep")?.sleep(ms) ?? sleep(ms)),
|
|
3028
|
+
flush: method((timeoutMs) => resolve("flush")?.flush(timeoutMs) ?? Promise.resolve()),
|
|
3080
3029
|
retry: method((fn, options) => {
|
|
3081
3030
|
const reporter = currentReporter();
|
|
3082
3031
|
return reporter ? reporter.context.retry(fn, options) : retryWithBackoff(fn, options);
|
|
3083
3032
|
}),
|
|
3084
3033
|
// ── processo ────────────────────────────────────────────────────────────────
|
|
3085
|
-
register: method((
|
|
3086
|
-
register(
|
|
3034
|
+
register: method((handler, options) => {
|
|
3035
|
+
register(handler, options);
|
|
3087
3036
|
installGlobalHobots();
|
|
3088
3037
|
}),
|
|
3089
|
-
start: method((
|
|
3090
|
-
start(
|
|
3038
|
+
start: method((handler, options) => {
|
|
3039
|
+
start(handler, options);
|
|
3091
3040
|
installGlobalHobots();
|
|
3092
3041
|
}),
|
|
3093
3042
|
close: method((timeoutMs) => close(timeoutMs)),
|