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.cjs
CHANGED
|
@@ -96,7 +96,7 @@ function scrubSecrets(text) {
|
|
|
96
96
|
|
|
97
97
|
// src/utils.ts
|
|
98
98
|
var SDK_NAME = "hobots";
|
|
99
|
-
var SDK_VERSION = true ? "
|
|
99
|
+
var SDK_VERSION = true ? "4.1.0" : "0.0.0-dev";
|
|
100
100
|
function isNode() {
|
|
101
101
|
return typeof process !== "undefined" && typeof process.versions?.node === "string";
|
|
102
102
|
}
|
|
@@ -281,7 +281,6 @@ var protocol_limits_default = {
|
|
|
281
281
|
MAX_LOGS_PER_PATCH: 100,
|
|
282
282
|
MAX_ERROR_MESSAGE_CHARS: 1e4,
|
|
283
283
|
MAX_ERROR_STACK_CHARS: 2e4,
|
|
284
|
-
MAX_ITEMS_PER_RUN: 1e4,
|
|
285
284
|
MAX_ITEMS_PER_REQUEST: 500,
|
|
286
285
|
MAX_ITEM_MESSAGE_CHARS: 2e3,
|
|
287
286
|
MAX_ITEM_PAYLOAD_BYTES: 16384,
|
|
@@ -580,7 +579,7 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
|
|
|
580
579
|
const origin = fromOption ? "clientSecret" : `env ${CLIENT_SECRET_ENV}`;
|
|
581
580
|
if (!key) {
|
|
582
581
|
throw new Error(
|
|
583
|
-
`${prefix} Client Secret ausente \u2014 defina a env ${CLIENT_SECRET_ENV} (ou passe clientSecret nas op\xE7\xF5es
|
|
582
|
+
`${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`
|
|
584
583
|
);
|
|
585
584
|
}
|
|
586
585
|
if (!SECRET_PATTERN.test(key)) {
|
|
@@ -593,7 +592,7 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
|
|
|
593
592
|
key,
|
|
594
593
|
baseUrl,
|
|
595
594
|
ingestEndpoint: `${baseUrl}/v1/ingest`,
|
|
596
|
-
|
|
595
|
+
runsBaseUrl: `${baseUrl}/v1/runs`
|
|
597
596
|
};
|
|
598
597
|
}
|
|
599
598
|
|
|
@@ -957,7 +956,7 @@ var TasksApi = class {
|
|
|
957
956
|
const { signal: timeoutSignal, cleanup } = withTimeoutSignal(UPDATE_TIMEOUT_MS, signal);
|
|
958
957
|
let response;
|
|
959
958
|
try {
|
|
960
|
-
response = await fetch(`${this.baseUrl}
|
|
959
|
+
response = await fetch(`${this.baseUrl}`, {
|
|
961
960
|
redirect: "error",
|
|
962
961
|
method: "POST",
|
|
963
962
|
headers: this.headers(),
|
|
@@ -971,12 +970,11 @@ var TasksApi = class {
|
|
|
971
970
|
throw new TasksApiError(`cria\xE7\xE3o da run falhou (HTTP ${response.status})`, response.status);
|
|
972
971
|
}
|
|
973
972
|
const body = await response.json().catch(() => null);
|
|
974
|
-
if (typeof body?.id !== "string"
|
|
975
|
-
throw new TasksApiError("resposta inv\xE1lida da cria\xE7\xE3o de run (id
|
|
973
|
+
if (typeof body?.id !== "string") {
|
|
974
|
+
throw new TasksApiError("resposta inv\xE1lida da cria\xE7\xE3o de run (id ausente)", 0);
|
|
976
975
|
}
|
|
977
976
|
return {
|
|
978
977
|
id: body.id,
|
|
979
|
-
task: body.task,
|
|
980
978
|
params: body.params && typeof body.params === "object" && !Array.isArray(body.params) ? body.params : {},
|
|
981
979
|
form: null,
|
|
982
980
|
source: typeof body.source === "string" ? body.source : "agent",
|
|
@@ -991,9 +989,9 @@ var TasksApi = class {
|
|
|
991
989
|
}
|
|
992
990
|
const valid = [];
|
|
993
991
|
for (const run of runs.slice(0, Math.max(0, max))) {
|
|
994
|
-
if (typeof run?.id !== "string"
|
|
992
|
+
if (typeof run?.id !== "string") continue;
|
|
995
993
|
const params = run.params && typeof run.params === "object" && !Array.isArray(run.params) ? run.params : {};
|
|
996
|
-
valid.push({ ...run, id: run.id,
|
|
994
|
+
valid.push({ ...run, id: run.id, params });
|
|
997
995
|
}
|
|
998
996
|
if (valid.length < runs.length) {
|
|
999
997
|
this.logger.warn(`poll retornou ${runs.length - valid.length} run(s) inv\xE1lida(s)/excedente(s) \u2014 descartadas`);
|
|
@@ -1008,7 +1006,7 @@ var TasksApi = class {
|
|
|
1008
1006
|
let response;
|
|
1009
1007
|
try {
|
|
1010
1008
|
response = await fetchWithRetry(
|
|
1011
|
-
() => fetch(`${this.baseUrl}
|
|
1009
|
+
() => fetch(`${this.baseUrl}/${encodeURIComponent(runId)}`, {
|
|
1012
1010
|
redirect: "error",
|
|
1013
1011
|
method: "PATCH",
|
|
1014
1012
|
headers: this.headers(),
|
|
@@ -1030,7 +1028,7 @@ var TasksApi = class {
|
|
|
1030
1028
|
* que a run já foi cancelada/finalizada, o que é aceitável nesse caminho.
|
|
1031
1029
|
*/
|
|
1032
1030
|
async updateRunOnce(runId, update, timeoutMs = 2e3) {
|
|
1033
|
-
await fetch(`${this.baseUrl}
|
|
1031
|
+
await fetch(`${this.baseUrl}/${encodeURIComponent(runId)}`, {
|
|
1034
1032
|
redirect: "error",
|
|
1035
1033
|
method: "PATCH",
|
|
1036
1034
|
headers: this.headers(),
|
|
@@ -1047,7 +1045,7 @@ var TasksApi = class {
|
|
|
1047
1045
|
let response;
|
|
1048
1046
|
try {
|
|
1049
1047
|
response = await fetchWithRetry(
|
|
1050
|
-
() => fetch(`${this.baseUrl}
|
|
1048
|
+
() => fetch(`${this.baseUrl}/${encodeURIComponent(runId)}/items`, {
|
|
1051
1049
|
redirect: "error",
|
|
1052
1050
|
method: "POST",
|
|
1053
1051
|
headers: this.headers(),
|
|
@@ -1077,7 +1075,7 @@ var TasksApi = class {
|
|
|
1077
1075
|
() => withRequestTimeout(
|
|
1078
1076
|
UPDATE_TIMEOUT_MS,
|
|
1079
1077
|
signal,
|
|
1080
|
-
(timeoutSignal) => fetch(`${this.baseUrl}
|
|
1078
|
+
(timeoutSignal) => fetch(`${this.baseUrl}/${encodeURIComponent(runId)}/attachments`, {
|
|
1081
1079
|
redirect: "error",
|
|
1082
1080
|
method: "POST",
|
|
1083
1081
|
headers: this.headers(),
|
|
@@ -1128,7 +1126,7 @@ var TasksApi = class {
|
|
|
1128
1126
|
/** Marca o anexo como enviado (ou falho). Best-effort — a leitura não depende disso. */
|
|
1129
1127
|
async confirmAttachment(runId, attachmentId, request) {
|
|
1130
1128
|
const response = await fetch(
|
|
1131
|
-
`${this.baseUrl}
|
|
1129
|
+
`${this.baseUrl}/${encodeURIComponent(runId)}/attachments/${encodeURIComponent(attachmentId)}`,
|
|
1132
1130
|
{
|
|
1133
1131
|
redirect: "error",
|
|
1134
1132
|
method: "PATCH",
|
|
@@ -1386,8 +1384,8 @@ function splitLogArgs(message, rest) {
|
|
|
1386
1384
|
if (text.length === 0) return { text: last.caption !== void 0 ? safeString(last.caption) : "captura de tela", image: last };
|
|
1387
1385
|
return { text: joinLogArgs(text[0], text.slice(1)), image: last };
|
|
1388
1386
|
}
|
|
1389
|
-
function printRunLog(
|
|
1390
|
-
const line = colorize(`[hobots
|
|
1387
|
+
function printRunLog(level, message) {
|
|
1388
|
+
const line = colorize(`[hobots] ${level.toUpperCase()}`, LOG_LEVEL_COLOR[level]);
|
|
1391
1389
|
const method2 = level === "warning" ? "warn" : level === "success" ? "info" : level;
|
|
1392
1390
|
console[method2](line, message);
|
|
1393
1391
|
}
|
|
@@ -1415,7 +1413,6 @@ var MAX_CANCEL_CHECK_INTERVAL_MS = 15e3;
|
|
|
1415
1413
|
var MAX_LOG_MESSAGE_CHARS = WIRE_LIMITS.MAX_LOG_MESSAGE_CHARS;
|
|
1416
1414
|
var MAX_LOGS_PER_PATCH = WIRE_LIMITS.MAX_LOGS_PER_PATCH;
|
|
1417
1415
|
var FLUSH_AT_BUFFERED = 20;
|
|
1418
|
-
var MAX_ITEMS_PER_RUN = WIRE_LIMITS.MAX_ITEMS_PER_RUN;
|
|
1419
1416
|
var MAX_ITEMS_PER_REQUEST = WIRE_LIMITS.MAX_ITEMS_PER_REQUEST;
|
|
1420
1417
|
var MAX_ITEM_MESSAGE_CHARS = WIRE_LIMITS.MAX_ITEM_MESSAGE_CHARS;
|
|
1421
1418
|
var MAX_ITEM_PAYLOAD_BYTES = WIRE_LIMITS.MAX_ITEM_PAYLOAD_BYTES;
|
|
@@ -1433,10 +1430,11 @@ function scrubPayloadValue(value, depth) {
|
|
|
1433
1430
|
return out;
|
|
1434
1431
|
}
|
|
1435
1432
|
var RunReporter = class {
|
|
1436
|
-
constructor(api, runId,
|
|
1433
|
+
constructor(api, runId, logger, tx, flushIntervalMs, cancelCheckIntervalMs, params = {}) {
|
|
1437
1434
|
this.api = api;
|
|
1438
1435
|
this.runId = runId;
|
|
1439
1436
|
this.logger = logger;
|
|
1437
|
+
this.tx = tx;
|
|
1440
1438
|
this.flushIntervalMs = flushIntervalMs ?? DEFAULT_LOG_FLUSH_INTERVAL_MS;
|
|
1441
1439
|
this.cancelCheckIntervalMs = Math.min(
|
|
1442
1440
|
MAX_CANCEL_CHECK_INTERVAL_MS,
|
|
@@ -1515,12 +1513,12 @@ var RunReporter = class {
|
|
|
1515
1513
|
const sleep2 = (ms) => cancelableSleep(ms, signal);
|
|
1516
1514
|
this.context = {
|
|
1517
1515
|
runId,
|
|
1518
|
-
task,
|
|
1519
1516
|
params,
|
|
1520
1517
|
log,
|
|
1521
1518
|
items,
|
|
1522
1519
|
progress,
|
|
1523
1520
|
transaction: tx,
|
|
1521
|
+
flush: (timeoutMs) => this.flush(timeoutMs),
|
|
1524
1522
|
signal,
|
|
1525
1523
|
throwIfCanceled: checkCanceled,
|
|
1526
1524
|
sleep: sleep2,
|
|
@@ -1530,6 +1528,7 @@ var RunReporter = class {
|
|
|
1530
1528
|
api;
|
|
1531
1529
|
runId;
|
|
1532
1530
|
logger;
|
|
1531
|
+
tx;
|
|
1533
1532
|
context;
|
|
1534
1533
|
buffer = [];
|
|
1535
1534
|
dead = false;
|
|
@@ -1547,7 +1546,6 @@ var RunReporter = class {
|
|
|
1547
1546
|
itemsChain = Promise.resolve();
|
|
1548
1547
|
itemsTimer;
|
|
1549
1548
|
itemSeq = 0;
|
|
1550
|
-
itemCount = 0;
|
|
1551
1549
|
/** Servidor sem o recurso (404) — para de tentar (e de avisar) nesta run. */
|
|
1552
1550
|
itemsUnsupported = false;
|
|
1553
1551
|
/** Cap de itens por run atingido (client-side ou 429) — degrada com um aviso só. */
|
|
@@ -1624,7 +1622,7 @@ var RunReporter = class {
|
|
|
1624
1622
|
const scrubbed = scrubSecrets(safeString(message));
|
|
1625
1623
|
const ts = nowISO();
|
|
1626
1624
|
const attachmentId = image3 === void 0 ? void 0 : this.queueAttachment(image3, ts);
|
|
1627
|
-
printRunLog(
|
|
1625
|
+
printRunLog(level, attachmentId ? `${scrubbed} [imagem]` : scrubbed);
|
|
1628
1626
|
this.push(level, scrubbed, true, attachmentId ? [attachmentId] : void 0, ts);
|
|
1629
1627
|
}
|
|
1630
1628
|
/** Bufferiza uma entrada de log sem eco no terminal (cap, truncate, flush). */
|
|
@@ -1670,6 +1668,21 @@ var RunReporter = class {
|
|
|
1670
1668
|
this.enqueue({ status: "running", logs: logs.slice(i, i + MAX_LOGS_PER_PATCH) });
|
|
1671
1669
|
}
|
|
1672
1670
|
}
|
|
1671
|
+
/**
|
|
1672
|
+
* `hobots.flush()`: o bot decide quando entregar o que já fez — steps raiz finalizadas
|
|
1673
|
+
* viram transactions parciais da run, e itens, logs e progresso pendentes saem já, sem
|
|
1674
|
+
* esperar o throttle. Aguarda a entrega com teto. Nunca lança (pode ir num `finally`).
|
|
1675
|
+
*/
|
|
1676
|
+
async flush(timeoutMs = ITEMS_DRAIN_MS) {
|
|
1677
|
+
this.tx.flush();
|
|
1678
|
+
this.flushItems();
|
|
1679
|
+
if (this.progressDirty) this.flushProgress();
|
|
1680
|
+
else this.flushLogs();
|
|
1681
|
+
await Promise.race([
|
|
1682
|
+
Promise.all([this.itemsChain, this.chain, this.tx.drain(timeoutMs)]).catch(() => void 0),
|
|
1683
|
+
new Promise((resolve2) => unrefTimer(setTimeout(resolve2, timeoutMs)))
|
|
1684
|
+
]);
|
|
1685
|
+
}
|
|
1673
1686
|
/** Primeiro PATCH (claimed → running). Retorna false se a run já morreu (cancelada). */
|
|
1674
1687
|
async reportRunning() {
|
|
1675
1688
|
this.enqueue({ status: "running" });
|
|
@@ -1780,14 +1793,6 @@ var RunReporter = class {
|
|
|
1780
1793
|
*/
|
|
1781
1794
|
addItem(status, message, payload, id, image3) {
|
|
1782
1795
|
if (this.itemsClosed || this.itemsUnsupported) return;
|
|
1783
|
-
if (this.itemCount >= MAX_ITEMS_PER_RUN) {
|
|
1784
|
-
if (!this.itemsQuotaExceeded) {
|
|
1785
|
-
this.itemsQuotaExceeded = true;
|
|
1786
|
-
this.log("warning", `itens descartados: limite de ${MAX_ITEMS_PER_RUN} por execu\xE7\xE3o atingido`);
|
|
1787
|
-
}
|
|
1788
|
-
return;
|
|
1789
|
-
}
|
|
1790
|
-
this.itemCount++;
|
|
1791
1796
|
let scrubbedPayload;
|
|
1792
1797
|
if (payload) {
|
|
1793
1798
|
scrubbedPayload = scrubPayloadValue(payload, 0);
|
|
@@ -2082,6 +2087,7 @@ function currentRunReporter() {
|
|
|
2082
2087
|
}
|
|
2083
2088
|
|
|
2084
2089
|
// src/transaction.ts
|
|
2090
|
+
var FLUSH_TRANSACTION_OP = "hobots.run.flush";
|
|
2085
2091
|
var MAX_STEP_MESSAGE_CHARS = 2e3;
|
|
2086
2092
|
function stepMessage(text) {
|
|
2087
2093
|
return truncate(scrubSecrets(text), MAX_STEP_MESSAGE_CHARS);
|
|
@@ -2269,7 +2275,7 @@ var Transaction = class {
|
|
|
2269
2275
|
if (this.attachmentSink) return this.attachmentSink(image3, nowISO());
|
|
2270
2276
|
if (!this.warnedNoAttachmentSink) {
|
|
2271
2277
|
this.warnedNoAttachmentSink = true;
|
|
2272
|
-
console.warn("[hobots] imagem de step descartada \u2014 s\xF3 \xE9 suportada dentro de uma execu\xE7\xE3o
|
|
2278
|
+
console.warn("[hobots] imagem de step descartada \u2014 s\xF3 \xE9 suportada dentro de uma execu\xE7\xE3o");
|
|
2273
2279
|
}
|
|
2274
2280
|
return void 0;
|
|
2275
2281
|
}
|
|
@@ -2303,6 +2309,60 @@ var Transaction = class {
|
|
|
2303
2309
|
this.name = name;
|
|
2304
2310
|
return this;
|
|
2305
2311
|
}
|
|
2312
|
+
/**
|
|
2313
|
+
* Envia já as steps raiz finalizadas (e todas as descendentes, fechando as que
|
|
2314
|
+
* ficaram abertas) como transactions parciais da mesma run (`op` hobots.run.flush,
|
|
2315
|
+
* `event_id` novo, até 200 steps cada) e as tira da memória. Steps raiz ainda abertas
|
|
2316
|
+
* ficam para o próximo flush ou para o `finish()`. É assim que um agente sempre
|
|
2317
|
+
* ligado (`start()`), cuja run não termina, entrega as etapas enquanto processa.
|
|
2318
|
+
* Devolve quantas steps foram enviadas.
|
|
2319
|
+
*/
|
|
2320
|
+
flush() {
|
|
2321
|
+
if (this.finished) return 0;
|
|
2322
|
+
const ready = /* @__PURE__ */ new Set();
|
|
2323
|
+
for (const step of this.steps) {
|
|
2324
|
+
if (step.parentStepId === this.stepId && step.endTimestamp) ready.add(step.stepId);
|
|
2325
|
+
}
|
|
2326
|
+
if (ready.size === 0) return 0;
|
|
2327
|
+
const batch = [];
|
|
2328
|
+
const remaining = [];
|
|
2329
|
+
for (const step of this.steps) {
|
|
2330
|
+
if (ready.has(step.stepId) || ready.has(step.parentStepId)) {
|
|
2331
|
+
ready.add(step.stepId);
|
|
2332
|
+
batch.push(step);
|
|
2333
|
+
} else {
|
|
2334
|
+
remaining.push(step);
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
for (const step of batch) step.finish();
|
|
2338
|
+
this.steps.splice(0, this.steps.length, ...remaining);
|
|
2339
|
+
if (this.client) {
|
|
2340
|
+
for (let i = 0; i < batch.length; i += MAX_STEPS) {
|
|
2341
|
+
const chunk = batch.slice(i, i + MAX_STEPS);
|
|
2342
|
+
const startTimestamp = chunk.reduce((min, step) => step.startTimestamp < min ? step.startTimestamp : min, chunk[0].startTimestamp);
|
|
2343
|
+
const status = chunk.some((step) => step.status === "error") ? "error" : "ok";
|
|
2344
|
+
this.client.sendTransaction(this.payload(uuid4(), FLUSH_TRANSACTION_OP, status, startTimestamp, nowISO(), chunk));
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
return batch.length;
|
|
2348
|
+
}
|
|
2349
|
+
/** Entrega já (com teto) o que está na fila do transport — as transactions parciais inclusive. */
|
|
2350
|
+
async drain(timeoutMs) {
|
|
2351
|
+
await this.client?.flush(timeoutMs);
|
|
2352
|
+
}
|
|
2353
|
+
payload(eventId, op, status, startTimestamp, timestamp, steps) {
|
|
2354
|
+
return {
|
|
2355
|
+
event_id: eventId,
|
|
2356
|
+
name: this.name,
|
|
2357
|
+
op,
|
|
2358
|
+
status,
|
|
2359
|
+
start_timestamp: startTimestamp,
|
|
2360
|
+
timestamp,
|
|
2361
|
+
steps: steps.map((step) => step.toJSON()),
|
|
2362
|
+
tags: Object.keys(this.tags).length > 0 ? { ...this.tags } : void 0,
|
|
2363
|
+
run_id: this.runId
|
|
2364
|
+
};
|
|
2365
|
+
}
|
|
2306
2366
|
finish() {
|
|
2307
2367
|
if (this.finished) return;
|
|
2308
2368
|
this.finished = true;
|
|
@@ -2317,31 +2377,27 @@ var Transaction = class {
|
|
|
2317
2377
|
this.status
|
|
2318
2378
|
);
|
|
2319
2379
|
if (!this.client) return;
|
|
2320
|
-
const
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
status
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
run_id: this.runId
|
|
2330
|
-
};
|
|
2331
|
-
this.client.sendTransaction(payload);
|
|
2380
|
+
const steps = this.steps;
|
|
2381
|
+
let first = 0;
|
|
2382
|
+
while (steps.length - first > MAX_STEPS) {
|
|
2383
|
+
const chunk = steps.slice(first, first + MAX_STEPS);
|
|
2384
|
+
const status = chunk.some((step) => step.status === "error") ? "error" : "ok";
|
|
2385
|
+
this.client.sendTransaction(this.payload(uuid4(), FLUSH_TRANSACTION_OP, status, chunk[0].startTimestamp, endTimestamp, chunk));
|
|
2386
|
+
first += MAX_STEPS;
|
|
2387
|
+
}
|
|
2388
|
+
this.client.sendTransaction(this.payload(this.eventId, this.op, this.status, this.startTimestamp, endTimestamp, steps.slice(first)));
|
|
2332
2389
|
}
|
|
2333
2390
|
};
|
|
2334
2391
|
|
|
2335
2392
|
// src/tasks/runner.ts
|
|
2336
2393
|
var DEFAULT_RETRY_DELAY_MS = 5e3;
|
|
2337
2394
|
async function executeRun(input) {
|
|
2338
|
-
const { run,
|
|
2339
|
-
const options =
|
|
2340
|
-
const tx = new Transaction(client,
|
|
2395
|
+
const { run, handler, api, client, logger, tracker } = input;
|
|
2396
|
+
const options = handler.options;
|
|
2397
|
+
const tx = new Transaction(client, "run", "hobots.run", run.id);
|
|
2341
2398
|
const reporter = new RunReporter(
|
|
2342
2399
|
api,
|
|
2343
2400
|
run.id,
|
|
2344
|
-
run.task,
|
|
2345
2401
|
logger,
|
|
2346
2402
|
tx,
|
|
2347
2403
|
options.logFlushIntervalMs,
|
|
@@ -2357,9 +2413,9 @@ async function executeRun(input) {
|
|
|
2357
2413
|
tx.finish();
|
|
2358
2414
|
return;
|
|
2359
2415
|
}
|
|
2360
|
-
logger.debug(`executando run ${run.id}
|
|
2416
|
+
logger.debug(`executando run ${run.id}`);
|
|
2361
2417
|
try {
|
|
2362
|
-
await runWithRunContext({ reporter }, () => invokeWithRetry(
|
|
2418
|
+
await runWithRunContext({ reporter }, () => invokeWithRetry(handler, run, reporter, logger));
|
|
2363
2419
|
await reporter.finalizeAttachments();
|
|
2364
2420
|
await reporter.finalizeItems();
|
|
2365
2421
|
warnIncompleteProgress(reporter);
|
|
@@ -2387,11 +2443,11 @@ function warnIncompleteProgress(reporter) {
|
|
|
2387
2443
|
`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`
|
|
2388
2444
|
);
|
|
2389
2445
|
}
|
|
2390
|
-
function invokeWithRetry(
|
|
2391
|
-
const { retries, retryDelayMs } =
|
|
2446
|
+
function invokeWithRetry(agent, run, reporter, logger) {
|
|
2447
|
+
const { retries, retryDelayMs } = agent.options;
|
|
2392
2448
|
const total = retries + 1;
|
|
2393
2449
|
const ctx = reporter.context;
|
|
2394
|
-
return retryWithBackoff(() => invokeWithTimeout(
|
|
2450
|
+
return retryWithBackoff(() => invokeWithTimeout(agent, run, reporter, logger), {
|
|
2395
2451
|
retries,
|
|
2396
2452
|
delayMs: (attempt) => resolveDelay(retryDelayMs, attempt, DEFAULT_RETRY_DELAY_MS),
|
|
2397
2453
|
shouldRetry: () => !reporter.isDead,
|
|
@@ -2405,9 +2461,9 @@ function invokeWithRetry(task, run, reporter, logger) {
|
|
|
2405
2461
|
}
|
|
2406
2462
|
var TIMEOUT_SENTINEL = /* @__PURE__ */ Symbol("task-timeout");
|
|
2407
2463
|
var CANCEL_SENTINEL = /* @__PURE__ */ Symbol("task-canceled");
|
|
2408
|
-
async function invokeWithTimeout(
|
|
2409
|
-
const { timeoutMs, cancelGraceMs } =
|
|
2410
|
-
const handler =
|
|
2464
|
+
async function invokeWithTimeout(agent, run, reporter, logger) {
|
|
2465
|
+
const { timeoutMs, cancelGraceMs } = agent.options;
|
|
2466
|
+
const handler = agent.handler;
|
|
2411
2467
|
const invocation = Promise.resolve().then(() => handler(run.params, reporter.context));
|
|
2412
2468
|
const races = [];
|
|
2413
2469
|
if (timeoutMs && timeoutMs > 0) {
|
|
@@ -2464,7 +2520,7 @@ var TaskAgent = class {
|
|
|
2464
2520
|
this.logger = options.logger;
|
|
2465
2521
|
this.api = options.api;
|
|
2466
2522
|
this.client = options.client;
|
|
2467
|
-
this.
|
|
2523
|
+
this.handler = options.handler;
|
|
2468
2524
|
this.tracker = options.tracker;
|
|
2469
2525
|
this.pollIntervalMs = Math.max(MIN_POLL_INTERVAL_MS, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
|
|
2470
2526
|
this.backoff = new Backoff(this.logger, "poll", this.pollIntervalMs);
|
|
@@ -2474,7 +2530,7 @@ var TaskAgent = class {
|
|
|
2474
2530
|
pollIntervalMs;
|
|
2475
2531
|
api;
|
|
2476
2532
|
client;
|
|
2477
|
-
|
|
2533
|
+
handler;
|
|
2478
2534
|
tracker;
|
|
2479
2535
|
backoff;
|
|
2480
2536
|
state = "idle";
|
|
@@ -2490,10 +2546,10 @@ var TaskAgent = class {
|
|
|
2490
2546
|
throw new Error("[hobots] start do agente durante o stop \u2014 aguarde o stop terminar");
|
|
2491
2547
|
}
|
|
2492
2548
|
this.state = "started";
|
|
2493
|
-
this.logger.debug(`polling iniciado (
|
|
2549
|
+
this.logger.debug(`polling iniciado (concorr\xEAncia ${this.handler.options.concurrency})`);
|
|
2494
2550
|
this.schedule(0);
|
|
2495
2551
|
}
|
|
2496
|
-
/**
|
|
2552
|
+
/** Antecipa o próximo poll para já. */
|
|
2497
2553
|
wake() {
|
|
2498
2554
|
if (this.state !== "started") return;
|
|
2499
2555
|
if (this.timer) {
|
|
@@ -2524,21 +2580,14 @@ var TaskAgent = class {
|
|
|
2524
2580
|
void this.tick();
|
|
2525
2581
|
}, delayMs);
|
|
2526
2582
|
}
|
|
2527
|
-
/** Slots livres
|
|
2583
|
+
/** Slots livres do agente — vira o `max` do poll (nunca reivindica trabalho para deixar parado). */
|
|
2528
2584
|
freeSlots() {
|
|
2529
|
-
const
|
|
2530
|
-
|
|
2531
|
-
for (const task of this.registry.remoteTasks()) {
|
|
2532
|
-
const free = task.options.concurrency - this.tracker.activeFor(task.slug);
|
|
2533
|
-
if (free <= 0) continue;
|
|
2534
|
-
tasks.push(task.slug);
|
|
2535
|
-
max += free;
|
|
2536
|
-
}
|
|
2537
|
-
return { tasks, max: Math.min(MAX_CLAIM_PER_POLL, max) };
|
|
2585
|
+
const free = this.handler.options.concurrency - this.tracker.activeCount;
|
|
2586
|
+
return Math.min(MAX_CLAIM_PER_POLL, Math.max(0, free));
|
|
2538
2587
|
}
|
|
2539
2588
|
async tick() {
|
|
2540
2589
|
if (this.state !== "started") return;
|
|
2541
|
-
const
|
|
2590
|
+
const max = this.freeSlots();
|
|
2542
2591
|
if (max === 0) {
|
|
2543
2592
|
this.schedule(this.pollIntervalMs);
|
|
2544
2593
|
return;
|
|
@@ -2548,11 +2597,9 @@ var TaskAgent = class {
|
|
|
2548
2597
|
const response = await this.api.poll(
|
|
2549
2598
|
{
|
|
2550
2599
|
// identidade canônica do agente no painel — o mesmo instanceId do heartbeat,
|
|
2551
|
-
// então os dois canais convergem em um agente
|
|
2552
|
-
// tasks (register + start); o filtro `tasks` abaixo só as que este poll pode rodar.
|
|
2600
|
+
// então os dois canais convergem em um agente
|
|
2553
2601
|
instance: this.options.instanceInfo(),
|
|
2554
|
-
max
|
|
2555
|
-
tasks
|
|
2602
|
+
max
|
|
2556
2603
|
},
|
|
2557
2604
|
this.pollAbort.signal
|
|
2558
2605
|
);
|
|
@@ -2576,17 +2623,7 @@ var TaskAgent = class {
|
|
|
2576
2623
|
this.activeRuns.add(promise);
|
|
2577
2624
|
}
|
|
2578
2625
|
async executeClaimed(run) {
|
|
2579
|
-
|
|
2580
|
-
if (!task || task.mode !== "remote") {
|
|
2581
|
-
const tx = new Transaction(this.client, `run:${run.task}`, "hobots.task", run.id);
|
|
2582
|
-
const reporter = new RunReporter(this.api, run.id, run.task, this.logger, tx);
|
|
2583
|
-
reporter.reportFailure(toRunError(Object.assign(new Error(`tarefa n\xE3o registrada no agente: ${run.task}`), { name: "HandlerNotFound" })));
|
|
2584
|
-
await reporter.finalize();
|
|
2585
|
-
tx.setStatus("error");
|
|
2586
|
-
tx.finish();
|
|
2587
|
-
return;
|
|
2588
|
-
}
|
|
2589
|
-
await executeRun({ run, task, api: this.api, client: this.client, logger: this.logger, tracker: this.tracker });
|
|
2626
|
+
await executeRun({ run, handler: this.handler, api: this.api, client: this.client, logger: this.logger, tracker: this.tracker });
|
|
2590
2627
|
}
|
|
2591
2628
|
};
|
|
2592
2629
|
|
|
@@ -2594,11 +2631,11 @@ var TaskAgent = class {
|
|
|
2594
2631
|
var KEY_RE = /^[a-zA-Z0-9_.-]{1,100}$/;
|
|
2595
2632
|
var MAX_COLUMNS = WIRE_LIMITS.MAX_ITEM_COLUMNS;
|
|
2596
2633
|
var MAX_LABEL_CHARS = WIRE_LIMITS.MAX_ITEM_COLUMN_LABEL_CHARS;
|
|
2597
|
-
function normalizeItemColumns(
|
|
2634
|
+
function normalizeItemColumns(input, logger) {
|
|
2598
2635
|
if (input === void 0 || input === null) return void 0;
|
|
2599
2636
|
const columns = input.columns;
|
|
2600
2637
|
if (typeof input !== "object" || !Array.isArray(columns)) {
|
|
2601
|
-
logger.warn(`
|
|
2638
|
+
logger.warn(`items.columns precisa ser uma lista \u2014 declara\xE7\xE3o de colunas ignorada`);
|
|
2602
2639
|
return void 0;
|
|
2603
2640
|
}
|
|
2604
2641
|
const result = [];
|
|
@@ -2608,15 +2645,15 @@ function normalizeItemColumns(slug, input, logger) {
|
|
|
2608
2645
|
const rawLabel = typeof entry === "object" && entry !== null ? entry.label : void 0;
|
|
2609
2646
|
const key = typeof rawKey === "string" ? rawKey.trim() : "";
|
|
2610
2647
|
if (!KEY_RE.test(key)) {
|
|
2611
|
-
logger.warn(`
|
|
2648
|
+
logger.warn(`coluna de item inv\xE1lida ignorada (${JSON.stringify(rawKey)}) \u2014 use letras, n\xFAmeros, ponto, h\xEDfen e underline`);
|
|
2612
2649
|
continue;
|
|
2613
2650
|
}
|
|
2614
2651
|
if (seen.has(key)) {
|
|
2615
|
-
logger.warn(`
|
|
2652
|
+
logger.warn(`coluna de item "${key}" repetida ignorada`);
|
|
2616
2653
|
continue;
|
|
2617
2654
|
}
|
|
2618
2655
|
if (result.length >= MAX_COLUMNS) {
|
|
2619
|
-
logger.warn(`
|
|
2656
|
+
logger.warn(`no m\xE1ximo ${MAX_COLUMNS} colunas de item \u2014 "${key}" em diante ignoradas`);
|
|
2620
2657
|
break;
|
|
2621
2658
|
}
|
|
2622
2659
|
seen.add(key);
|
|
@@ -2628,15 +2665,15 @@ function normalizeItemColumns(slug, input, logger) {
|
|
|
2628
2665
|
|
|
2629
2666
|
// src/tasks/local.ts
|
|
2630
2667
|
var LocalRunner = class {
|
|
2631
|
-
constructor(
|
|
2632
|
-
this.
|
|
2668
|
+
constructor(handler, deps) {
|
|
2669
|
+
this.handler = handler;
|
|
2633
2670
|
this.deps = deps;
|
|
2634
|
-
this.backoff = new Backoff(deps.logger,
|
|
2671
|
+
this.backoff = new Backoff(deps.logger, "cria\xE7\xE3o da run");
|
|
2635
2672
|
this.done = new Promise((resolve2) => {
|
|
2636
2673
|
this.resolveDone = resolve2;
|
|
2637
2674
|
});
|
|
2638
2675
|
}
|
|
2639
|
-
|
|
2676
|
+
handler;
|
|
2640
2677
|
deps;
|
|
2641
2678
|
abort = new AbortController();
|
|
2642
2679
|
backoff;
|
|
@@ -2647,17 +2684,17 @@ var LocalRunner = class {
|
|
|
2647
2684
|
try {
|
|
2648
2685
|
const run = await this.createRun();
|
|
2649
2686
|
if (!run) return;
|
|
2650
|
-
this.deps.logger.debug(`run ${run.id} aberta
|
|
2687
|
+
this.deps.logger.debug(`run ${run.id} aberta (modo start)`);
|
|
2651
2688
|
await executeRun({
|
|
2652
2689
|
run,
|
|
2653
|
-
|
|
2690
|
+
handler: this.handler,
|
|
2654
2691
|
api: this.deps.api,
|
|
2655
2692
|
client: this.deps.client,
|
|
2656
2693
|
logger: this.deps.logger,
|
|
2657
2694
|
tracker: this.deps.tracker
|
|
2658
2695
|
});
|
|
2659
2696
|
} catch (error) {
|
|
2660
|
-
this.deps.logger.error(
|
|
2697
|
+
this.deps.logger.error("erro inesperado na execu\xE7\xE3o do agente:", error);
|
|
2661
2698
|
} finally {
|
|
2662
2699
|
this.resolveDone();
|
|
2663
2700
|
}
|
|
@@ -2670,7 +2707,7 @@ var LocalRunner = class {
|
|
|
2670
2707
|
for (; ; ) {
|
|
2671
2708
|
if (this.abort.signal.aborted) return void 0;
|
|
2672
2709
|
try {
|
|
2673
|
-
return await this.deps.api.createRun({
|
|
2710
|
+
return await this.deps.api.createRun({ instance: this.deps.instanceInfo() }, this.abort.signal);
|
|
2674
2711
|
} catch (error) {
|
|
2675
2712
|
if (this.abort.signal.aborted) return void 0;
|
|
2676
2713
|
await abortableSleep(this.backoff.next(error), this.abort.signal);
|
|
@@ -2680,45 +2717,6 @@ var LocalRunner = class {
|
|
|
2680
2717
|
};
|
|
2681
2718
|
|
|
2682
2719
|
// src/tasks/registry.ts
|
|
2683
|
-
var TaskRegistry = class {
|
|
2684
|
-
tasks = /* @__PURE__ */ new Map();
|
|
2685
|
-
add(task) {
|
|
2686
|
-
if (this.tasks.has(task.slug)) {
|
|
2687
|
-
throw new Error(`[hobots] task "${task.slug}" j\xE1 registrada \u2014 cada slug s\xF3 pode ter um handler (register ou start)`);
|
|
2688
|
-
}
|
|
2689
|
-
this.tasks.set(task.slug, task);
|
|
2690
|
-
}
|
|
2691
|
-
get(slug) {
|
|
2692
|
-
return this.tasks.get(slug);
|
|
2693
|
-
}
|
|
2694
|
-
get size() {
|
|
2695
|
-
return this.tasks.size;
|
|
2696
|
-
}
|
|
2697
|
-
/** Tasks que chegam pelo poll (register). */
|
|
2698
|
-
remoteTasks() {
|
|
2699
|
-
return [...this.tasks.values()].filter((task) => task.mode === "remote");
|
|
2700
|
-
}
|
|
2701
|
-
/** Tasks abertas pelo próprio bot (start). */
|
|
2702
|
-
localTasks() {
|
|
2703
|
-
return [...this.tasks.values()].filter((task) => task.mode === "local");
|
|
2704
|
-
}
|
|
2705
|
-
/** Todos os slugs, para a presença da instância (`instance.tasks`). */
|
|
2706
|
-
allSlugs() {
|
|
2707
|
-
return [...this.tasks.keys()];
|
|
2708
|
-
}
|
|
2709
|
-
/** Colunas de itens declaradas no código, por slug (`instance.task_items`); undefined se nenhuma task declarou. */
|
|
2710
|
-
itemDeclarations() {
|
|
2711
|
-
let result;
|
|
2712
|
-
for (const task of this.tasks.values()) {
|
|
2713
|
-
if (!task.options.itemColumns) continue;
|
|
2714
|
-
(result ??= {})[task.slug] = { attributes: task.options.itemColumns };
|
|
2715
|
-
}
|
|
2716
|
-
return result;
|
|
2717
|
-
}
|
|
2718
|
-
clear() {
|
|
2719
|
-
this.tasks.clear();
|
|
2720
|
-
}
|
|
2721
|
-
};
|
|
2722
2720
|
var RunTracker = class {
|
|
2723
2721
|
constructor(api) {
|
|
2724
2722
|
this.api = api;
|
|
@@ -2739,12 +2737,6 @@ var RunTracker = class {
|
|
|
2739
2737
|
get activeCount() {
|
|
2740
2738
|
return this.reporters.size;
|
|
2741
2739
|
}
|
|
2742
|
-
/** Runs ativas de uma task (concorrência por task no poll). */
|
|
2743
|
-
activeFor(slug) {
|
|
2744
|
-
let count = 0;
|
|
2745
|
-
for (const reporter of this.reporters) if (reporter.context.task === slug) count++;
|
|
2746
|
-
return count;
|
|
2747
|
-
}
|
|
2748
2740
|
/** A única run ativa, quando há exatamente uma — o que `hobots.*` usa fora de um contexto async. */
|
|
2749
2741
|
singleActive() {
|
|
2750
2742
|
if (this.reporters.size !== 1) return void 0;
|
|
@@ -2833,40 +2825,13 @@ var DEFAULT_RETRIES2 = 1;
|
|
|
2833
2825
|
var DEFAULT_RETRY_DELAY_MS2 = 5e3;
|
|
2834
2826
|
var DEFAULT_CANCEL_GRACE_MS = 1e4;
|
|
2835
2827
|
var MAX_CONCURRENCY = 5;
|
|
2836
|
-
var MAX_SLUG_CHARS = 255;
|
|
2837
|
-
var PROCESS_LEVEL_KEYS = [
|
|
2838
|
-
"clientSecret",
|
|
2839
|
-
"instanceId",
|
|
2840
|
-
"environment",
|
|
2841
|
-
"release",
|
|
2842
|
-
"tags",
|
|
2843
|
-
"debug",
|
|
2844
|
-
"machineMetrics",
|
|
2845
|
-
"captureUnhandled",
|
|
2846
|
-
"installExitHandlers"
|
|
2847
|
-
];
|
|
2848
2828
|
var runtime;
|
|
2849
2829
|
var warnedNoRuntime = false;
|
|
2850
2830
|
function hostname() {
|
|
2851
2831
|
if (!isNode()) return void 0;
|
|
2852
2832
|
return process.env.HOSTNAME || process.env.COMPUTERNAME || void 0;
|
|
2853
2833
|
}
|
|
2854
|
-
function
|
|
2855
|
-
if (a === b) return true;
|
|
2856
|
-
if (a && b && typeof a === "object" && typeof b === "object") return JSON.stringify(a) === JSON.stringify(b);
|
|
2857
|
-
return false;
|
|
2858
|
-
}
|
|
2859
|
-
function ensureRuntime(options) {
|
|
2860
|
-
if (runtime) {
|
|
2861
|
-
for (const key of PROCESS_LEVEL_KEYS) {
|
|
2862
|
-
if (options[key] !== void 0 && !sameValue(options[key], runtime.options[key])) {
|
|
2863
|
-
runtime.logger.warn(
|
|
2864
|
-
`op\xE7\xE3o "${key}" divergente ignorada \u2014 o primeiro register()/start() define as op\xE7\xF5es do processo`
|
|
2865
|
-
);
|
|
2866
|
-
}
|
|
2867
|
-
}
|
|
2868
|
-
return runtime;
|
|
2869
|
-
}
|
|
2834
|
+
function createRuntime(options, handler) {
|
|
2870
2835
|
const config = resolveConfig({ clientSecret: options.clientSecret });
|
|
2871
2836
|
const logger = makeLogger(options.debug ?? false);
|
|
2872
2837
|
const environment = resolveEnvironmentOption(options.environment);
|
|
@@ -2874,19 +2839,19 @@ function ensureRuntime(options) {
|
|
|
2874
2839
|
{ environment, release: options.release, tags: options.tags, debug: options.debug },
|
|
2875
2840
|
config
|
|
2876
2841
|
);
|
|
2877
|
-
const api = new TasksApi({ baseUrl: config.
|
|
2878
|
-
|
|
2842
|
+
const api = new TasksApi({ baseUrl: config.runsBaseUrl, key: config.key, logger });
|
|
2843
|
+
const rt = {
|
|
2879
2844
|
options: { ...options, clientSecret: void 0 },
|
|
2880
2845
|
config,
|
|
2881
2846
|
client,
|
|
2882
2847
|
logger,
|
|
2883
2848
|
api,
|
|
2884
|
-
|
|
2849
|
+
handler,
|
|
2885
2850
|
tracker: new RunTracker(api),
|
|
2886
2851
|
sampler: void 0,
|
|
2887
2852
|
heartbeat: void 0,
|
|
2888
2853
|
agent: void 0,
|
|
2889
|
-
|
|
2854
|
+
local: void 0,
|
|
2890
2855
|
instanceId: resolveInstanceId(options.instanceId),
|
|
2891
2856
|
environment,
|
|
2892
2857
|
pollIntervalMs: void 0,
|
|
@@ -2896,24 +2861,19 @@ function ensureRuntime(options) {
|
|
|
2896
2861
|
uninstallGlobalHandlers: options.captureUnhandled !== false ? installGlobalHandlers(client) : void 0,
|
|
2897
2862
|
uninstallShutdown: void 0
|
|
2898
2863
|
};
|
|
2899
|
-
runtime.options.clientSecret = options.clientSecret;
|
|
2900
2864
|
logger.debug("inicializado", { endpoint: config.ingestEndpoint });
|
|
2901
|
-
return
|
|
2865
|
+
return rt;
|
|
2902
2866
|
}
|
|
2903
|
-
function
|
|
2904
|
-
if (typeof slug !== "string" || slug.trim().length === 0 || slug.length > MAX_SLUG_CHARS) {
|
|
2905
|
-
throw new Error(`[hobots] ${fn}(): o slug da task precisa ser uma string de 1 a ${MAX_SLUG_CHARS} caracteres`);
|
|
2906
|
-
}
|
|
2867
|
+
function validateHandler(handler, fn) {
|
|
2907
2868
|
if (typeof handler !== "function") {
|
|
2908
|
-
throw new Error(`[hobots] ${fn}(
|
|
2869
|
+
throw new Error(`[hobots] ${fn}(): o handler precisa ser uma fun\xE7\xE3o \u2014 ${fn}(async (params) => { ... })`);
|
|
2909
2870
|
}
|
|
2910
|
-
return slug.trim();
|
|
2911
2871
|
}
|
|
2912
2872
|
function clampConcurrency(value) {
|
|
2913
2873
|
if (value === void 0 || !Number.isFinite(value)) return 1;
|
|
2914
2874
|
return Math.min(MAX_CONCURRENCY, Math.max(1, Math.floor(value)));
|
|
2915
2875
|
}
|
|
2916
|
-
function
|
|
2876
|
+
function resolveRunOptions(options, mode, logger) {
|
|
2917
2877
|
const retries = options.retries === void 0 || !Number.isFinite(options.retries) ? DEFAULT_RETRIES2 : Math.max(0, Math.floor(options.retries));
|
|
2918
2878
|
return {
|
|
2919
2879
|
retries,
|
|
@@ -2923,7 +2883,7 @@ function resolveTaskOptions(slug, options, mode, logger) {
|
|
|
2923
2883
|
logFlushIntervalMs: options.logFlushIntervalMs,
|
|
2924
2884
|
cancelCheckIntervalMs: options.cancelCheckIntervalMs,
|
|
2925
2885
|
cancelGraceMs: options.cancelGraceMs ?? DEFAULT_CANCEL_GRACE_MS,
|
|
2926
|
-
itemColumns: normalizeItemColumns(
|
|
2886
|
+
itemColumns: normalizeItemColumns(options.items, logger)
|
|
2927
2887
|
};
|
|
2928
2888
|
}
|
|
2929
2889
|
function instanceInfo(rt) {
|
|
@@ -2933,10 +2893,10 @@ function instanceInfo(rt) {
|
|
|
2933
2893
|
host: hostname(),
|
|
2934
2894
|
// ambiente da instância: junto do heartbeat, é como o servidor sabe onde o robô roda
|
|
2935
2895
|
environment: rt.environment,
|
|
2936
|
-
//
|
|
2937
|
-
|
|
2896
|
+
// como esta instância trabalha — o painel mostra "sob demanda" / "sempre ligado"
|
|
2897
|
+
mode: rt.handler.mode === "remote" ? "on_demand" : "always_on",
|
|
2938
2898
|
// colunas da aba "itens" declaradas no código — a plataforma usa enquanto o usuário não personalizar
|
|
2939
|
-
|
|
2899
|
+
items: rt.handler.options.itemColumns ? { attributes: rt.handler.options.itemColumns } : void 0,
|
|
2940
2900
|
// o servidor calcula a tolerância de offline (3×) a partir disto em agentes que fazem
|
|
2941
2901
|
// poll; clampado à faixa aceita pelo schema (1s..15min)
|
|
2942
2902
|
poll_interval_ms: rt.agent ? Math.min(Math.max(rt.agent.pollIntervalMs, 1e3), 9e5) : void 0,
|
|
@@ -2949,22 +2909,22 @@ function ensureAgent(rt) {
|
|
|
2949
2909
|
api: rt.api,
|
|
2950
2910
|
client: rt.client,
|
|
2951
2911
|
logger: rt.logger,
|
|
2952
|
-
|
|
2912
|
+
handler: rt.handler,
|
|
2953
2913
|
tracker: rt.tracker,
|
|
2954
2914
|
instanceInfo: () => instanceInfo(rt),
|
|
2955
2915
|
pollIntervalMs: rt.pollIntervalMs
|
|
2956
2916
|
});
|
|
2957
2917
|
return rt.agent;
|
|
2958
2918
|
}
|
|
2959
|
-
function launchLocal(rt
|
|
2960
|
-
const runner = new LocalRunner(
|
|
2919
|
+
function launchLocal(rt) {
|
|
2920
|
+
const runner = new LocalRunner(rt.handler, {
|
|
2961
2921
|
api: rt.api,
|
|
2962
2922
|
client: rt.client,
|
|
2963
2923
|
logger: rt.logger,
|
|
2964
2924
|
tracker: rt.tracker,
|
|
2965
2925
|
instanceInfo: () => instanceInfo(rt)
|
|
2966
2926
|
});
|
|
2967
|
-
rt.
|
|
2927
|
+
rt.local = runner;
|
|
2968
2928
|
void runner.run();
|
|
2969
2929
|
}
|
|
2970
2930
|
async function boot(rt) {
|
|
@@ -2986,8 +2946,8 @@ async function boot(rt) {
|
|
|
2986
2946
|
if (rt.options.installExitHandlers !== false) {
|
|
2987
2947
|
rt.uninstallShutdown = installShutdownHandlers(rt.tracker);
|
|
2988
2948
|
}
|
|
2989
|
-
if (rt.
|
|
2990
|
-
|
|
2949
|
+
if (rt.handler.mode === "remote") ensureAgent(rt).start();
|
|
2950
|
+
else launchLocal(rt);
|
|
2991
2951
|
}
|
|
2992
2952
|
function scheduleBoot(rt) {
|
|
2993
2953
|
if (rt.bootScheduled) return;
|
|
@@ -2996,34 +2956,23 @@ function scheduleBoot(rt) {
|
|
|
2996
2956
|
rt.bootPromise = boot(rt).catch((error) => rt.logger.error("falha ao iniciar o SDK:", error));
|
|
2997
2957
|
});
|
|
2998
2958
|
}
|
|
2999
|
-
function
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
if (rt.pollIntervalMs === void 0 && !rt.agent) rt.pollIntervalMs = options.pollIntervalMs;
|
|
3004
|
-
else if (rt.pollIntervalMs !== options.pollIntervalMs) {
|
|
3005
|
-
rt.logger.warn('op\xE7\xE3o "pollIntervalMs" divergente ignorada \u2014 o primeiro register() define o intervalo do poll');
|
|
3006
|
-
}
|
|
3007
|
-
}
|
|
3008
|
-
const registered = { slug, mode, handler, options: resolveTaskOptions(slug, options, mode, rt.logger) };
|
|
3009
|
-
rt.registry.add(registered);
|
|
3010
|
-
if (!rt.booted) {
|
|
3011
|
-
scheduleBoot(rt);
|
|
3012
|
-
return;
|
|
3013
|
-
}
|
|
3014
|
-
if (mode === "remote") {
|
|
3015
|
-
const agent = ensureAgent(rt);
|
|
3016
|
-
agent.start();
|
|
3017
|
-
agent.wake();
|
|
3018
|
-
} else {
|
|
3019
|
-
launchLocal(rt, registered);
|
|
2959
|
+
function startAgent(fn, mode, handler, options) {
|
|
2960
|
+
validateHandler(handler, fn);
|
|
2961
|
+
if (runtime) {
|
|
2962
|
+
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)`);
|
|
3020
2963
|
}
|
|
2964
|
+
const logger = makeLogger(options.debug ?? false);
|
|
2965
|
+
const agentHandler = { mode, handler, options: resolveRunOptions(options, mode, logger) };
|
|
2966
|
+
const rt = createRuntime(options, agentHandler);
|
|
2967
|
+
if (mode === "remote" && options.pollIntervalMs !== void 0) rt.pollIntervalMs = options.pollIntervalMs;
|
|
2968
|
+
runtime = rt;
|
|
2969
|
+
scheduleBoot(rt);
|
|
3021
2970
|
}
|
|
3022
|
-
function register(
|
|
3023
|
-
|
|
2971
|
+
function register(handler, options = {}) {
|
|
2972
|
+
startAgent("register", "remote", handler, options);
|
|
3024
2973
|
}
|
|
3025
|
-
function start(
|
|
3026
|
-
|
|
2974
|
+
function start(handler, options = {}) {
|
|
2975
|
+
startAgent("start", "local", handler, options);
|
|
3027
2976
|
}
|
|
3028
2977
|
function currentReporter() {
|
|
3029
2978
|
return currentRunReporter() ?? runtime?.tracker.singleActive();
|
|
@@ -3037,7 +2986,7 @@ function toRunLogLevel(type) {
|
|
|
3037
2986
|
function forwardAttachment(rt, attachment, message, type) {
|
|
3038
2987
|
const reporter = currentReporter();
|
|
3039
2988
|
if (!reporter) {
|
|
3040
|
-
rt.logger.warn("attachment descartado \u2014 s\xF3 \xE9 suportado dentro de uma execu\xE7\xE3o
|
|
2989
|
+
rt.logger.warn("attachment descartado \u2014 s\xF3 \xE9 suportado dentro de uma execu\xE7\xE3o");
|
|
3041
2990
|
return;
|
|
3042
2991
|
}
|
|
3043
2992
|
const withMeta = typeof attachment === "object" && !isImageAttachment(attachment) && "image" in attachment ? image(attachment.image, { caption: attachment.caption, contentType: attachment.contentType }) : attachment;
|
|
@@ -3048,7 +2997,7 @@ function captureException(error, hint) {
|
|
|
3048
2997
|
if (!rt) {
|
|
3049
2998
|
if (!warnedNoRuntime) {
|
|
3050
2999
|
warnedNoRuntime = true;
|
|
3051
|
-
console.warn("[hobots] captureException antes de
|
|
3000
|
+
console.warn("[hobots] captureException antes de iniciar o agente (register/start) \u2014 evento descartado");
|
|
3052
3001
|
}
|
|
3053
3002
|
return void 0;
|
|
3054
3003
|
}
|
|
@@ -3064,7 +3013,7 @@ async function close(timeoutMs = 3e4) {
|
|
|
3064
3013
|
runtime = void 0;
|
|
3065
3014
|
await rt.bootPromise;
|
|
3066
3015
|
if (rt.agent) await rt.agent.stop(0);
|
|
3067
|
-
|
|
3016
|
+
rt.local?.stop();
|
|
3068
3017
|
const idle = await rt.tracker.waitIdle(timeoutMs);
|
|
3069
3018
|
if (!idle) {
|
|
3070
3019
|
rt.logger.warn(`${rt.tracker.activeCount} run(s) ainda em execu\xE7\xE3o ap\xF3s ${timeoutMs}ms \u2014 abandonando a espera`);
|
|
@@ -3082,15 +3031,14 @@ var warned = /* @__PURE__ */ new Set();
|
|
|
3082
3031
|
function warnOutsideRun(member) {
|
|
3083
3032
|
if (warned.has(member)) return;
|
|
3084
3033
|
warned.add(member);
|
|
3085
|
-
const message = `hobots.${member} usado fora de uma execu\xE7\xE3o
|
|
3034
|
+
const message = `hobots.${member} usado fora de uma execu\xE7\xE3o \u2014 ignorado (inicie o agente com register()/start() e use dentro do handler)`;
|
|
3086
3035
|
const logger = activeLogger();
|
|
3087
3036
|
if (logger) logger.warn(message);
|
|
3088
3037
|
else console.warn(`[hobots] ${message}`);
|
|
3089
3038
|
}
|
|
3090
|
-
var FALLBACK_TASK = "hobots";
|
|
3091
3039
|
var fallbackLogAt = (level) => (message, ...rest) => {
|
|
3092
3040
|
const { text, image: dropped } = splitLogArgs(message, rest);
|
|
3093
|
-
printRunLog(
|
|
3041
|
+
printRunLog(level, dropped ? `${text} [imagem descartada]` : text);
|
|
3094
3042
|
};
|
|
3095
3043
|
var fallbackLog = {
|
|
3096
3044
|
debug: fallbackLogAt("debug"),
|
|
@@ -3107,7 +3055,7 @@ var fallbackEachSink = {
|
|
|
3107
3055
|
advance: noop,
|
|
3108
3056
|
checkCanceled: noop,
|
|
3109
3057
|
isAborted: () => false,
|
|
3110
|
-
logError: (message) => printRunLog(
|
|
3058
|
+
logError: (message) => printRunLog("error", message)
|
|
3111
3059
|
};
|
|
3112
3060
|
var fallbackItems = {
|
|
3113
3061
|
add: noop,
|
|
@@ -3134,7 +3082,6 @@ function method(value) {
|
|
|
3134
3082
|
var hobots = Object.defineProperties({}, {
|
|
3135
3083
|
// ── contexto da run atual (getters) ─────────────────────────────────────────
|
|
3136
3084
|
runId: getter("runId", () => ""),
|
|
3137
|
-
task: getter("task", () => ""),
|
|
3138
3085
|
params: getter("params", () => ({})),
|
|
3139
3086
|
log: getter("log", () => fallbackLog),
|
|
3140
3087
|
items: getter("items", () => fallbackItems),
|
|
@@ -3143,17 +3090,18 @@ var hobots = Object.defineProperties({}, {
|
|
|
3143
3090
|
signal: getter("signal", () => neverAborts),
|
|
3144
3091
|
throwIfCanceled: method(() => resolve("throwIfCanceled")?.throwIfCanceled()),
|
|
3145
3092
|
sleep: method((ms) => resolve("sleep")?.sleep(ms) ?? sleep(ms)),
|
|
3093
|
+
flush: method((timeoutMs) => resolve("flush")?.flush(timeoutMs) ?? Promise.resolve()),
|
|
3146
3094
|
retry: method((fn, options) => {
|
|
3147
3095
|
const reporter = currentReporter();
|
|
3148
3096
|
return reporter ? reporter.context.retry(fn, options) : retryWithBackoff(fn, options);
|
|
3149
3097
|
}),
|
|
3150
3098
|
// ── processo ────────────────────────────────────────────────────────────────
|
|
3151
|
-
register: method((
|
|
3152
|
-
register(
|
|
3099
|
+
register: method((handler, options) => {
|
|
3100
|
+
register(handler, options);
|
|
3153
3101
|
installGlobalHobots();
|
|
3154
3102
|
}),
|
|
3155
|
-
start: method((
|
|
3156
|
-
start(
|
|
3103
|
+
start: method((handler, options) => {
|
|
3104
|
+
start(handler, options);
|
|
3157
3105
|
installGlobalHobots();
|
|
3158
3106
|
}),
|
|
3159
3107
|
close: method((timeoutMs) => close(timeoutMs)),
|